gps 태그 앨범 제작(5/4)

구글 지도가 지원하는 clusterer를 사용하고 싶다. cpan으로 설치한 HTML:GoogleMap:V3 내부를 손봐야 했다. perl이 사용하는 perl module @INC(경로 비슷한?)를 찾았다.

root@gen8# perl -V
Summary of my perl5 (revision 5 version 30 subversion 1) configuration:
   
  Platform:
    osname=linux
...
  @INC:
    /usr/local/lib/perl5/site_perl/5.30.1/x86_64-linux-gnu
    /usr/local/lib/perl5/site_perl/5.30.1
    /usr/local/lib/perl5/vendor_perl/5.30.1/x86_64-linux-gnu
    /usr/local/lib/perl5/vendor_perl/5.30.1
    /usr/local/lib/perl5/5.30.1/x86_64-linux-gnu
    /usr/local/lib/perl5/5.30.1

usr/local/lib 경로 아래 V3.pm을 찾아 지웠더니 에러를 받았다. 야호! perl을 잘 모르지만 내부를 조금 수정하면 내가 원하는 코드를 만들 수 있다. 여기에서 add marker 부분만 조금 수정했다. template을 사용하여 쉽게 수정했다.

...V3.pm 수정.
...
   my $template =<<"EndOfTemplate";
function html_googlemaps_initialize() {

    [% # Github issue 22 %]
    [% IF center %]
	myCenterLatLng = new google.maps.LatLng({lat: [% center.0 %], lng: [% center.1 %]});
    [% END %]


	//2020.2.16 추가
	var markers = [];

    // key map controls
    var map = new google.maps.Map(document.getElementById('[% id %]'), {
        mapTypeId: google.maps.MapTypeId.[% type %],
        [% IF center %]center: myCenterLatLng,[% END %]
        scrollwheel: false,
        zoom: [% zoom %],
        draggable: [% dragging ? 'true' : 'false' %]
    });

    [% FOREACH point IN points %]

    // marker
    myMarker[% loop.count %]LatLng = new google.maps.LatLng({lat: [% point.point.0 %], lng: [% point.point.1 %]});
    var marker[% loop.count %] = new google.maps.Marker({
        map: map,
        position: myMarker[% loop.count %]LatLng,
    });

    // marker infoWindow
    [% IF info_window AND point.html %]
    var contentString[% loop.count %] = '[% point.html %]';
    var infowindow[% loop.count %] = new google.maps.InfoWindow({
        content: contentString[% loop.count %]
    });

    marker[% loop.count %].addListener('click', function() {
        infowindow[% loop.count %].open(map, marker[% loop.count %]);
    });

	//2020.2.16 추가
	markers.push(marker[% loop.count %]);

    [% END %]

    [% END -%]

    [% FOREACH route IN poly_lines %]

    // polylines
    var route[% loop.count %]Coordinates = [
        [% FOREACH point IN route.points %]{lat: [% point.0 %], lng: [% point.1 %]}[% loop.last ? '' : ',' %]
        [% END %]
    ];

    var route[% loop.count %] = new google.maps.Polyline({
        path: route[% loop.count %]Coordinates,
        geodesic: true,
        strokeColor: '[% route.color %]',
        strokeOpacity: [% route.opacity %],
        strokeWeight: [% route.weight %]
    });

    route[% loop.count %].setMap(map);
    [% END %]
	//20.2.16 추가
	var markerCluster = new MarkerClusterer(map, markers);
}
google.maps.event.addDomListener(window, 'load', html_googlemaps_initialize);

EndOfTemplate
}
...생략
sub onload_render {
    my ( $self ) = @_;

    # Add in all the defaults
    $self->{id}         ||= 'map';
    $self->{height}     ||= '400px';
    $self->{width}      ||= '600px';
    $self->{type}       ||= "NORMAL";
    $self->{zoom}       ||= 13;
    $self->{center}     ||= $self->_find_center;
    $self->{dragging}     = 1 unless defined $self->{dragging};
    $self->{info_window}  = 1 unless defined $self->{info_window};

    $self->{width}  .= 'px' if $self->{width} =~ m/^\d+$/;
    $self->{height} .= 'px' if $self->{height} =~ m/^\d+$/;

	#	    my $header = '<script src="https://maps.googleapis.com/maps/api/js__KEY__"'
	#        . ' async defer type="text/javascript"></script>'
	#    ;
	# 20.2.16 추가
	#  필요에 따라 경로 수정

	    my $header = '<script src="../map/marker/src/markerclusterer.js"></script>
<script src="https://maps.googleapis.com/maps/api/js__KEY__"'
	        . ' async defer type="text/javascript"></script>'
	    ;

    my $key = $self->{api_key}
        ? "?key=@{[ $self->{api_key} ]}" : "";

    $header =~ s/__KEY__/$key/;

    my $map = sprintf(
        '<div id="%s" style="width: %s; height: %s%s"></div>',
        @{$self}{qw/ id width height / },
        exists($self->{'z_index'})
            ? '; z-index: ' . $self->{'z_index'} : ''
    );

    my $out;
    Template->new->process( \$self->_js_template,$self,\$out );

    $header .= "<script>$out</script>";

    return ( $header,$map );
}
...생략

대충 원하는대로 나온다.

gps 태그 앨범 제작(4/4)

제길. perl 제작한 프로그램에 버그가 있다. mysql 접근 권한을 update를 주면 perl이 접속하면서 gps 정보를 초기화한다. perl 조회 계정을 하나 만들어 select 권한만 주었다.

그리고 GPS에 N,S, E, W 네 방향이 있다. 완전 좌절..

잘못한 update 칼럼을 찾아 다시 좌표를 넣어줘야 한다.

#!/bin/bash

#file 유무 확인
if [ ! -f $1 ];then
	echo "file is not there";
	exit
fi

#N, S, E, W 방향 확인.

GpsRefNS=$(exiv2 -pa $1 2> /dev/null | grep -ae "Exif\.GPSInfo\.GPSLatitudeRef.*Ascii" | sed 's/ \{1,\}/ /g'|cut -d' ' -f4-)
GpsRefEW=$(exiv2 -pa $1 2> /dev/null | grep -ae "Exif\.GPSInfo\.GPSLongitudeRef.*Ascii" | sed 's/ \{1,\}/ /g'|cut -d' ' -f4-)

if [ -z "$GpsRefNS" ]
then
	exit;
fi;

if [ -z "$GpsRefEW" ]
then
	exit;
fi;

GpsLat=$(exiv2 -pa $1 2> /dev/null | grep -ae "Exif\.GPSInfo\.GPSLatitude.*Rational" | sed 's/ \{1,\}/ /g' | cut -d' ' -f4-)
GpsLong=$(exiv2 -pa $1 2> /dev/null | grep -ae "Exif\.GPSInfo\.GPSLongitude.*Rational" | sed 's/ \{1,\}/ /g'| cut -d' ' -f4-)

#GpsLat마지막 숫자
GpsLatSec=$(echo $GpsLat | cut -d' ' -f3 | tr -d "\"")
GpsLatMin=$(echo $GpsLat | cut -d' ' -f2 | tr -d "\'")

#초가 없는 경우 0을 입력.
if [ -z "$GpsLatSec" ]
	then
		GpsLatSec=0;
fi

GpsLongSec=$(echo $GpsLong | cut -d' ' -f3 | tr -d "\"")
GpsLongMin=$(echo $GpsLong | cut -d' ' -f2 | tr -d "\'")

if [ -z "$GpsLongSec" ]
	then
		GpsLongSec=0;
fi

#GPS자표가 숫자인지 확인
re='^[0-9]{1,3}$'
#가끔 소수점도 있음..
re='^[0-9]{1,3}(\.[0-9]{1,5}$|$)'

if ! [[ $GpsLatSec =~ $re ]]
then
	exit 1;
fi

if ! [[ $GpsLatMin =~ $re ]]
then
	exit 1;
fi

GpsLatFloat=$(bc <<< "scale=4;($GpsLatSec/60+$GpsLatMin)/60")
GpsLongFloat=$(bc <<< "scale=4;($GpsLongSec/60+$GpsLongMin)/60")


#$Gps 인티저
GpsLatInt=$(echo $GpsLat | cut -d' ' -f1 | tr -d "d-g");
GpsLongInt=$(echo $GpsLong | cut -d' ' -f1 | tr -d "d-g");

#$전체 숫자.
GpsLatFull=$(bc <<< "scale=4;$GpsLatFloat+$GpsLatInt");
GpsLongFull=$(bc <<< "scale=4;$GpsLongFloat+$GpsLongInt");

#South, West면 반전
if [ "$GpsRefNS" = "South" ]
then
	GpsLatFull=$(bc <<< "scale=4;$GpsLatFull*-1");
fi

if [ "$GpsRefEW" = "West" ]
then
	GpsLongFull=$(bc <<< "scale=4;$GpsLongFull*-1");
fi

echo $GpsLatFull,$GpsLongFull;

이제 사진에 태그와 perl이 표시할 수 있는 내용을 입력해야 한다. perl에서 추가 모듈이 한글로 된 칼럼은 조회할 수 없다. 동일한 내용으로 영어로 만들었다. 이것도 개노가다.

전에 만든 php를 활용하여 사진을 업데이트 했다.

gps 태그 앨범 제작(3/4)

perl을 쓰기 위해 docker를 사용했다. commit대신 간단한 Dockerrun을 만들었다. commit으로 이미지를 만들면 다음에 다시 만들 수 없다한다.

FROM perl:v5.30.1
MAINTAINER ???
RUN apt-get -y update
#GooglaMaps::V3 설치
RUN cpan HTML::GoogleMaps::V3
#https 설정.
RUN cpan install LMP::UserAgen Mozilla::CA
RUN cpan install LWP::Protocol::https
RUN cpan install Bundle::DBI
RUN cpan install DBD::mysql

host에 있는 mysql에 접속하기 위해 옵션을 docker에 run 옵션을 줬다.

docker run -it --network=host -e LC_ALL=C.UTF-8 -v ????:/home/ perl:mysql_Map /bin/bash

DBI:mysql로 연결할 때 띄어쓰면 에러를 볼 수 있다. 붙여써야 한다. 이걸 모르고 하루를 날렸다. 또 한글 출력에 문제가 있는데 binmode를 주석처리 했다.

se utf8;
use strict;
use warnings;
use DBI;
use HTML::GoogleMaps::V3;
#아래내용은 구글MAPs 에 사용.
#binmode STDOUT, ":utf8";

my $host ="127.0.0.1";
my $user = "???";
my $password = "?????";
my $database = "?????";
my $tablesname =  "????";

#모두 붙여서 사용..
#space 있으면 connect 에러
my $dbh = DBI->connect("DBI:mysql:database=$database;host=$host", $user, $password, {RaiseError => 1});

#한글 설정
my $setkorean="SET NAMES utf8";
$dbh->do($setkorean);
#query 설정.
#my $query = "select * from picture where 태그 like '%??%' limit 10";
#테스트용 query
my $query = "select * from picture where (태그 like '%??%' and gpsLatFloat != 0) limit 10";

#query 실행
#my $sth=$dbh->prepare($query);
#$sth->execute();

#while(my $ref = $sth->fetchrow_hashref()){
#	print "Found: $ref->{'perl_tag'}";
#}
#print $sth->rows;

my $map = HTML::GoogleMaps::V3->new(
    api_key => "???"
);

#query 실행
my $sth=$dbh->prepare($query);
$sth->execute();

while(my $ref = $sth->fetchrow_hashref()){
	print "Found: $ref->{'perl_tag'}";
	$map->add_marker(
		point => [$ref->{'gpsLongFloat'},$ref->{'gpsLatFloat'}],
		html => qq{<a href=$ref->{perl_path}>$ref->{perl_tag}</a>},
		);
};#while loop

$sth->finish();
$dbh->disconnect();

my ( $head, $map_div ) = $map->onload_render;

print <<"END_HTML";
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
END_HTML

print $head . "\n";

print <<"END_HTML";
</head>
<body onload="html_googlemaps_initialize()">
END_HTML

print $map_div . "\n";

print <<"END_HTML";
</body>
</html>
END_HTML

이제 marker infoWindows를 이쁘게 설정하면 된다. 아무래도 설명을 입력해야 할 듯 하다.

gps 태그 앨범 제작(2/4)

몇 번 해보니 gps 좌표가 여러 형식이다. 삼성 카메라로 3개 형식을 얻을 수 있다.

Exif.GPSInfo.GPSLatitude                     Rational    3  37deg 34' 0.982"
Exif.GPSInfo.GPSLatitude                     Rational    3  37deg 34' 23"
Exif.GPSInfo.GPSLatitude                     Rational    3  35.4328992deg

모두 해당하는 경우를 하나 파일로 만들려 했으나, 틀을 깨어 3개 파일로 만들었다. 1번 파일이 2번 파일을 실행하고, 결과를 확인한다. 만족하지 못하면 3번 파일을 실행한다. 정규 표현식 검증은 여기에서 했다.

#!/bin/bash

#1번 스크립트
#echo "hello"
echo "$1로 파일 path를 입력"
#업데이트할 파일 숫자 확인
#echo $1
iMaxNum=$(wc -l $1 | cut -d' '  -f1)
#echo $iMaxNum

cat $1 |\
while read tmpFILE;
do
	tmpID=$(echo $tmpFILE | cut -d' ' -f1)
	tmpPATH=$(echo $tmpFILE | cut -d' ' -f2)
	#echo $tmpID, $tmpPATH;
	gpsCoord=$(/home/now0930/script/사진에서정보추출/gpsTextToFloat.sh $tmpPATH)
	#echo $gpsCoord;
	#echo $tmpPATH
	#echo "첫번째 실행"

	if [ -z "$gpsCoord" ]
	then
		gpsCoord=$(/home/now0930/script/사진에서정보추출/gpsTextToFloatMore.sh $tmpPATH)
		#echo "두번째 실행"
		#echo $gpsCoord;

	fi


	#echo "여기확인"
	#echo $tmpID, $gpsCoord;

	gpsCoordLat=$(echo $gpsCoord | cut -d',' -f1)
	gpsCoordLong=$(echo $gpsCoord | cut -d',' -f2)

	#float 형식인지 확인하여 mysql update

	#echo $tmpID
	#echo $tmpID, $gpsCoordLat, $gpsCoordLong;


	

	if ! [ -z "$gpsCoordLat" ]
	then
		#데이터 베이스 업데이트
		mysql << EOF

		use myHome;
		select id, 경로 from picture where id = $tmpID;
		update picture set gpsLatFloat=$gpsCoordLat, gpsLongFloat=$gpsCoordLong where id=$tmpID;
EOF

	#echo $gpsCoordLat, $gpsCoordLong

	fi
done
#!/bin/bash
#2번 스크립트.
#file 유무 확인
if [ ! -f $1 ];then
	echo "file is not there";
	exit
fi


#Trap으로 metadata를 가지고 있는지 확인.
#trap "exiv2 -pa  $1" EXIT
#echo $1
GpsLat=$(exiv2 -pa $1 2> /dev/null | grep -ae "Exif\.GPSInfo\.GPSLatitude.*Rational" | sed 's/ \{1,\}/ /g' | cut -d' ' -f4-)
GpsLong=$(exiv2 -pa $1 2> /dev/null | grep -ae "Exif\.GPSInfo\.GPSLongitude.*Rational" | sed 's/ \{1,\}/ /g'| cut -d' ' -f4-)

#echo $GpsLat;
#echo $GpsLong;
#printf "\n"

#GpsLat마지막 숫자
GpsLatSec=$(echo $GpsLat | cut -d' ' -f3 | tr -d "\"")
GpsLatMin=$(echo $GpsLat | cut -d' ' -f2 | tr -d "\'")

#초가 없는 경우 0을 입력.
if [ -z "$GpsLatSec" ]
	then
		GpsLatSec=0;
fi
#echo $GpsLatSec;
#echo $GpsLatMin;


GpsLongSec=$(echo $GpsLong | cut -d' ' -f3 | tr -d "\"")
GpsLongMin=$(echo $GpsLong | cut -d' ' -f2 | tr -d "\'")


if [ -z "$GpsLongSec" ]
	then
		GpsLongSec=0;
fi

#echo $GpsLatSec;
#echo $GpsLatMin;

#echo $GpsLongSec;
#echo $GpsLongMin;

#GPS자표가 숫자인지 확인
re='^[0-9]{1,3}$'
#가끔 소수점도 있음..
re='^[0-9]{1,3}(\.[0-9]{1,5}$|$)'

#if [ -z "$GpsLat" ]
#then
#	echo "0,0"
#	exit;
#fi
#echo $GpsLatSec, $GpsLongSec

if ! [[ $GpsLatSec =~ $re ]]
then
	exit 1;
fi

if ! [[ $GpsLatMin =~ $re ]]
then
	exit 1;
fi

GpsLatFloat=$(bc <<< "scale=4;($GpsLatSec/60+$GpsLatMin)/60")
GpsLongFloat=$(bc <<< "scale=4;($GpsLongSec/60+$GpsLongMin)/60")

#echo $GpsLatFloat
#echo $GpsLongFloat

#$Gps 인티저
GpsLatInt=$(echo $GpsLat | cut -d' ' -f1 | tr -d "d-g");
GpsLongInt=$(echo $GpsLong | cut -d' ' -f1 | tr -d "d-g");

#echo $GpsLatInt
#echo $GpsLongInt

#$전체 숫자.
GpsLatFull=$(bc <<< "scale=4;$GpsLatFloat+$GpsLatInt");
GpsLongFull=$(bc <<< "scale=4;$GpsLongFloat+$GpsLongInt");


echo $GpsLatFull,$GpsLongFull;
#!/bin/bash


#Trap으로 metadata를 가지고 있는지 확인.
#trap "exiv2 -pa  $1" EXIT

#echo $GpsLat
#echo $GpsLong
#echo "구분자"
#여기 확인..
#GPS 형식 확인
#xxx.xxxxdeg 형식일 경우 처리.
renumberdeg='^[0-9]{1,}\.[0-9]{1,}deg'
if [[ $GpsLat =~ $renumberdeg ]]
then
	#echo "여기 매치"
	GpsLatFull=$(echo $GpsLat| cut -d'd' -f1)
	#echo $GpsLatFull
fi

if [[ $GpsLong =~ $renumberdeg ]]
then
	GpsLongFull=$(echo $GpsLong | cut -d'd' -f1)
	#echo $GpsLongFull
fi
echo $GpsLatFull,$GpsLongFull;

이렇게 하면 gps 태그 달린 사진을 8개 남기고 입력할 수 있다. 나머지 8개는 포기한다.

gps 태그 앨범 제작(1/4)

휴대폰으로 gps 태그를 포함하여 사진을 저장할 수 있다. 보통 사진을 너무 많이 찍는데 기억할만한 장소를 표시하고 싶다. 역시 구글링을 해보니 perl로 제작한 좋은 페이지를 찾았다.

http://advent.perl.kr/2016/2016-12-06.html
https://metacpan.org/pod/HTML::GoogleMaps::V3

사진에 저장된 gps 태그를 보면 삼성 카메라 경우 각도+분+초로 되어있다. 이를 소수점으로 변환해야 한다. 공식은 (초/60 + 분)/60+각도다.

하나 문제가 있는데 사진을 저장할 때 숫자로 저장하지 않아 뒤쪽 소수점을 다 날렸다. 사진 경로를 찾아 파일에 저장된 정확한 gps 위치를 기록해야 한다.

가지고 있는 데이터베이스를 날릴 수 없어, id와 경로를 보고 위도, 경도를 업데이트 하기로 했다. 일단 위 식에 맞는 스크립트를 만들었다.

#!/bin/bash

#$1은 파일 경로. 데이터베이스에서 추출.
#file 유무 확인
if [ ! -f $1 ];then
	echo "file is not there";
	exit
fi
GpsLat=$(exiv2 -pa $1 2> /dev/null | grep -ae "Exif\.GPSInfo\.GPSLatitude.*Rational" | sed 's/ \{1,\}/ /g' | cut -d' ' -f4-)
GpsLong=$(exiv2 -pa $1 2> /dev/null | grep -ae "Exif\.GPSInfo\.GPSLongitude.*Rational" | sed 's/ \{1,\}/ /g'| cut -d' ' -f4-)

#echo $GpsLat;
#echo $GpsLong;

#GpsLat마지막 숫자
GpsLatSec=$(echo $GpsLat | cut -d' ' -f3 | tr -d "\"")
GpsLatMin=$(echo $GpsLat | cut -d' ' -f2 | tr -d "\'")

GpsLongSec=$(echo $GpsLong | cut -d' ' -f3 | tr -d "\"")
GpsLongMin=$(echo $GpsLong | cut -d' ' -f2 | tr -d "\'")

#echo $GpsLatSec
#echo $GpsLatMin

GpsLatFloat=$(bc <<< "scale=4;($GpsLatSec/60+$GpsLatMin)/60")
GpsLongFloat=$(bc <<< "scale=4;($GpsLongSec/60+$GpsLongMin)/60")

#echo $GpsLatFloat
#echo $GpsLongFloat

#$Gps 인티저
GpsLatInt=$(echo $GpsLat | cut -d' ' -f1 | tr -d "d-g");
GpsLongInt=$(echo $GpsLong | cut -d' ' -f1 | tr -d "d-g");

#echo $GpsLatInt
#echo $GpsLongInt

#전체 숫자.
GpsLatFull=$(bc <<< "scale=4;$GpsLatFloat+$GpsLatInt");
GpsLongFull=$(bc <<< "scale=4;$GpsLongFloat+$GpsLongInt");

echo $GpsLatFull;
echo $GpsLongFull;

이제 데이터베이스를 내용을 파일로 출력하여 경로를 위 스크립트 인자로 넣어주면 된다.