Статьи

Ionic / Cordova Demo: где я сделал этот снимок?

Время от времени я думаю о идее для классного (иначе бесполезного и бессмысленного, но веселого) приложения, которое, я думаю, займет у меня один час и позволит мне вырастить мою маленькую маленькую империю демонстраций. Иногда эти «быстрые маленькие демонстрации» в конечном итоге превращаются в многочасовые сеансы, когда я выдергиваю волосы, пытаясь выяснить, почему то или другое не работает. Это чертовски неприятно, пока я работаю над этим, но в итоге это делает меня таким же счастливым.

улыбка котята-большие

Почему? Потому что, если у меня возникнут проблемы с моей маленькой «игрушечной» демонстрацией, скорее всего, вы, бедный читатель, которому придется мириться с моими глупыми демонстрациями, столкнетесь с ним в производственном приложении. И если моя боль поможет вам избежать проблем, то этот блог заработает. Хорошо, так в чем была идея?

Несколько недель назад я делал покупки с моей женой. Это был тип магазина, в котором меня почти ничего не интересовало, поэтому я просто бездумно следил за ним. Но когда моя жена указала на что-то, что ей понравилось, я осторожно сфотографировал предмет, чтобы запомнить его как возможный подарок на день рождения или Рождество. К сожалению, я не мог вспомнить название магазина. Я знал окольную, где это было, конечно, но не фактический магазин.

Оказывается, что многие снимки автоматически включают данные, относящиеся к месту, где был сделан снимок. Вы можете — с помощью нескольких кликов — получить широту и долготу изображения. Это хорошо, но, честно говоря, я не могу перевести эти значения в «реальное» место, находящееся на вершине моей головы. Я уверен, что существуют веб-приложения, чтобы помочь с этим, но я подумал, не было бы неплохо, если бы я мог просто выбрать фотографию и попросить ее сказать, где она была сделана — на английском языке? Например:

Shot1

Для моей демонстрации я решил построить следующее:

  • Позвольте пользователю выбрать изображение.
  • Попытайтесь прочитать данные EXIF ​​и получить местоположение.
  • Попробуйте Foursquare это место. Я подумал, что это будет отлично работать для бизнеса.
  • Если это не помогло, попробуйте изменить геокодирование по крайней мере на адрес.
  • Если и это не поможет, покажите это хотя бы на карте.

Я сразу столкнулся с некоторыми интересными проблемами. Сначала мне нужно было прочитать данные EXIF. Я нашел для него плагин Cordova, но он не обновлялся в течение двух лет, и я видел несколько сообщений, которые не были устранены. Тогда я просто погуглил для «exif javascript» и наткнулся на этот проект: exif-js . Этот проект также был стар с выдающимися пиарщиками, но я подумал, что было бы безопаснее попробовать.

По большей части, это просто работает. Вот фрагмент, показывающий это в действии:

$scope.selectPicture = function() {
navigator.camera.getPicture(gotPic, errHandler, {
sourceType:Camera.PictureSourceType.PHOTOLIBRARY,
destinationType:Camera.DestinationType.NATIVE_URI
});
};

var errHandler = function(e) {
alert('Error with Camera: '+e);
};

//utility funct based on https://en.wikipedia.org/wiki/Geographic_coordinate_conversion
var convertDegToDec = function(arr) {
return (arr[0].numerator + arr[1].numerator/60 + (arr[2].numerator/arr[2].denominator)/3600).toFixed(4);
};

var gotPic = function(u) {
console.log('Got image '+u);
$scope.img.url = u;
//scope.apply can KMA
$scope.$apply();

};

var img = document.querySelector("#selImage");

img.addEventListener("load", function() {
console.log("load event for image "+(new Date()));
$scope.status.text = "Loading EXIF data for image.";
EXIF.getData(document.querySelector("#selImage"), function() {
console.log("in exif");

//console.dir(EXIF.getAllTags(img));
var long = EXIF.getTag(img,"GPSLongitude");
var lat = EXIF.getTag(img,"GPSLatitude");
if(!long || !lat) {
$scope.status.text = "Unfortunately, I can't find GPS info for the picture";
return;
}
long = convertDegToDec(long);
lat = convertDegToDec(lat);
//handle W/S
if(EXIF.getTag(this,"GPSLongitudeRef") === "W") long = -1 * long;
if(EXIF.getTag(this,"GPSLatitudeRef") === "S") lat = -1 * lat;
console.log(long,lat);
locateAddress(long,lat);
});
}, false);

Первое, что я обнаружил, было то, что когда вы выбираете изображение в Cordova, данные EXIF ​​сокращаются примерно до 4 или около того разных тегов. Оказывается, это известная ошибка ( CF-1285 ) из-за того, что плагин копирует исходное изображение и в этом процессе удаляет данные. Ошибка помечена как исправленная, но, очевидно, это не так. Однако если вы переключите источник камеры на NATIVE_URI, то проблема исчезнет.

Все идет нормально. Чтобы работать с кодом, необходимо указать его на изображение в DOM и дождаться окончания загрузки изображения. Это само по себе не сложно, хотя я чувствую себя грязно, когда использую DOM в контроллерах Angular. (Я преодолел это.) Затем я обнаружил проблему с библиотекой. Когда он загружает данные EXIF, он копирует значения в элемент DOM для кэширования. Я использую одно и то же изображение каждый раз, когда вы выбираете новую фотографию, поэтому это означало, что данные тега были кэшированы. Я подал отчет об ошибке и тем временем просто отредактировал библиотеку, чтобы убрать проверку кеша. Это плохо — но я тоже это пережил.

Следующей вещью, с которой мне пришлось поработать, был материал о местоположении. Как я уже сказал, идея состояла в том, чтобы сначала проверить Foursquare, вернуться к обратному геокодированию и снова вернуться к статической карте. Давайте сначала посмотрим на код контроллера.

var locateAddress = function(long,lat) {

$scope.status.text = "Trying to locate the photo.";

Location.getInfo(long, lat).then(function(result) {
console.log('Result was '+JSON.stringify(result));
if(result.type === 'foursquare') {
$scope.status.text = 'Your photo was taken at ' + result.name + ' located at ' + result.address;
} else if (result.type === 'geocode') {
$scope.status.text = 'Your photo appears to have been taken at ' + result.address;
} else {
var map = 'https://maps.googleapis.com/maps/api/staticmap?center='+lat+','+long+'zoom=13&size=300x300&maptype=roadmap&markers=color:blue%7Clabel:X%7C'+lat+','+long;
$scope.status.text = 'Sorry, I\'ve got nothing. But here is a map!';
}
});
};

Не слишком сложно, правда? Я просто запускаю свой сервис и разбираюсь с результатом. Сервис немного сложен, но на самом деле он использует различные API, которые я использую.

angular.module('starter.services', [])

.factory('Foursquare', function($http) {

var CLIENT_ID = 'mahsecretismahsecret';
var CLIENT_SECRET = 'soylentgreenispeople';

function whatsAt(long,lat) {
return $http.get('https://api.foursquare.com/v2/venues/search?ll='+lat+','+long+'&intent=browse&radius=30&client_id='+CLIENT_ID+'&client_secret='+CLIENT_SECRET+'&v=20151201');
}

return {
whatsAt:whatsAt
};
})
.factory('Geocode', function($http) {
var KEY = 'google should let me geocode for free';

function lookup(long,lat) {
return $http.get('https://maps.googleapis.com/maps/api/geocode/json?latlng='+lat+','+long+'&key='+KEY);
}

return {
lookup:lookup
};

})
.factory('Location', function($q,Foursquare,Geocode) {

function getInfo(long,lat) {
console.log('ok, in getInfo with '+long+','+lat);
var deferred = $q.defer();
Foursquare.whatsAt(long,lat).then(function(result) {
//console.log('back from fq with '+JSON.stringify(result));
if(result.status === 200 && result.data.response.venues.length >= 1) {
var bestMatch = result.data.response.venues[0];
//convert the result to something the caller can use consistently
var result = {
type:"foursquare",
name:bestMatch.name,
address:bestMatch.location.formattedAddress.join(", ")
}
console.dir(bestMatch);
deferred.resolve(result);
} else {
//ok, time to try google
Geocode.lookup(long,lat).then(function(result) {
console.log('back from google with ');
if(result.data && result.data.results && result.data.results.length >= 1) {
console.log('did i come in here?');
var bestMatch = result.data.results[0];
console.log(JSON.stringify(bestMatch));
var result = {
type:"geocode",
address:bestMatch.formatted_address
}
deferred.resolve(result);
}
});
}
});

return deferred.promise;
}
return {
getInfo:getInfo
};

});

В обоих случаях я предполагаю, что первый результат от API — лучший результат. Это не всегда может быть правдой, но пока работает. Вы видели пример работы Foursquare, вот пример обратного геокода.

геокод

И вот он с последним отступлением. Да, это та же самая картина, я просто временно отключил сервис Geocode для быстрого тестирования.

карта

В общем, это было забавное маленькое приложение для сборки, и, как я уже сказал, я рад, что столкнулся с проблемами EXIF. Я знаю, что мне это понадобится в будущем. Вы можете найти полный исходный код для этой демонстрации здесь: https://github.com/cfjedimaster/Cordova-Examples/tree/master/photolocate