Post view

解決insoler社群網站照片EXIF附屬的GPS資訊,無法顯示Google地圖的問題

上傳到insoler社群網站的照片,如果照片的EXIF有附屬GPS地圖資訊的話,會使用Google地圖自動顯示照片的拍攝地點。但是不知道從什麼時候開始,就無法顯示GPS地圖!我上網搜尋很久,才知道原來是Google在一年前就變更了使用規則!主要是:

1. 不再支援無金鑰存取Google地圖

2. 每日免費25,000次地圖載入

簡單的說,Google地圖不再免費使用,想要靠地圖資訊獲利!對一個幾乎沒有會員,也沒有營利的insoler小網站來說,或許每日免費25,000次的限制也很夠用。雖然無奈,也只能配合新的Google Maps API標準方案。


標準方案更新  |  Google Maps API 定價與方案  |  Google Developers

2016 年 6 月 22 日實施的 Google Maps API 標準方案更新如下:

  1. 我們不再支援無金鑰存取(即不包含 API 金鑰的任何要求)。未來的產品更新僅適用於以 API 金鑰做出的要求。
  2. 針對新的 Google Maps JavaScript API、Static Maps API 及 Street View Image API 實作,我們已實作簡單 25,000 地圖載入每日免費限制。現有應用程式的連續 90 天寬限期將在 2016 年 10 月 12 日 淘汰。
  3. 我們已將針對 Google Maps JavaScript API、Static Maps API 和 Street View Image API 的可購買每日地圖載入上限從 1,000,000 個要求降低為 100,000 個要求。需要更高配額的開發人員,請與銷售人員聯絡,討論進階方案授權。
  4. 我們現在將 Google Maps JavaScript API 用戶端服務要求計入相關 Web 服務的每日限制。

針對 2016 年 6 月 22 日當日或之後建立的應用程式

2016 年 6 月 22 日當日或之後建立的所有新網域或 Google API Console 專案應遵守更新後的政策與限制(如上文和我們的定價與方案頁面中所述),但以下情況除外:

  • 使用 Maps JavaScript API、Static Maps API 或 Street View Image API 應用程式的新實作可超過 25,000 次地圖載入每日免費限制,直到 2016 年 10 月 12 日 為止。
  • 2016 年 10 月 12 日 之後,如果您的應用程式超過 25,000 次地圖載入每日免費限制,API 在當天的剩餘時間都不會運作,除非您啟用計費功能來存取更高的每日配額。
  • 如果您已針對 Maps JavaScript API、Static Maps API 或 Street View Image API 啟用計費功能,而您超過 25,000 次地圖載入每日免費限制,我們不會針對超過的地圖載入向您收費,直到 2016 年 10 月 12 日 為止。
  • 如需詳細資料,請參閱每個 API 的使用限制頁面: Maps JavaScript API、 Static Maps API 或 Street View Image API。

針對 2016 年 6 月 22 日之前建立的應用程式

2016 年 6 月 22 日之前建立的所有現有使用中網域和應用程式不受新規則限制。具體詳細資料請見下文。

  1. 無金鑰存取

    2016 年 6 月 22 日之前建立的使用中網域可繼續存取 Google Maps JavaScript API、Static Maps API 和 Street View Image API 且不需要 API 金鑰。新網域無法使用的無金鑰存取,對它們沒有影響。

    針對 Google Maps Geocoding API、Directions API、Distance Matrix API、Elevation API 和 Time Zone API,已針對無金鑰要求設定了共用的整體配額,以確保現有應用程式不會中斷。我們日後並不打算提高此配額限制,建議所有開發人員使用金鑰,以保證其服務品質。如果碰到其他無金鑰應用程式使用高峰而降低整體配額,那麼持續使用無金鑰要求的應用程式可能會遭遇一些間歇性的服務品質降低。

  2. 針對地圖載入超過每天 25,000 個的 90 天連續寬限期

    對於擁有 2016 年 6 月 22 日前建立的使用中網域,並已針對 Maps JavaScript API、Static Maps API 或 Street View Image API 支付超過免費限制之每日配額的客戶:您不受此變更的影響。

    對於擁有 2016 年 6 月 22 日前建立的使用中網域,並使用 Maps JavaScript API、Static Maps API 或 Street View Image API 超過每日 25,000 次地圖載入,但目前沒有收取超額費用的客戶:在 2016 年 10 月 12 日前不會對您計費。如果您啟用計費功能,在 2016 年 10 月 12 日之後,這些網域將收取超過 25,000 次免費每日限制的地圖載入費用。如果您選擇不啟用計費功能,而您超過每日限制,則超過 25,000 次的地圖載入將會收到錯誤,且 API 在當天的剩餘時間都不會運作。

    深入了解額外用量與計費。

  3. 100,000 次每日地圖載入上限例外狀況

    如果您是擁有 2016 年 6 月 22 日前建立之使用中網域的客戶,而且使用 Maps JavaScript API、Static Maps API 或 Street View Image API 每天超過 80,000 次地圖載入,您可以繼續購買高達目前(截至 2016 年 6 月 22 日)最高每日使用量加 25% 的配額。此例外狀況可讓您繼續使用標準方案,即使超過新版標準方案 100,000 次地圖載入的每日可購買上限,直到您的使用量成長超過每日最高使用量 25% 為止。

  4. 用戶端 Web 服務要求配額補助

    Web 服務要求每天有高達 2,500 個免費要求,現在是以用戶端 JavaScript 查詢和 Web 服務 API 伺服器端查詢的加總來計算。擁有 2016 年 6 月 22 日前建立之使用中網域,並使用 Google Maps JavaScript API 向 Google Maps Geocoding API、Directions API、Distance Matrix API 或 Elevation API 發出用戶端服務要求的客戶,在該 Web 服務 API 上會獲得等於您目前(截至 2016 年 6 月 22 日)每日最高使用量加 25% 的免費用戶端配額。這表示您無須開始支付該 API 上的用戶端要求,直到您的每日使用量成長超過 25% 為止。您仍必須付款才能進行每日超過 2,500 個伺服器端要求,與此項變更之前的情況一樣。


舊版的Google地圖,只要按照Google提供的範例就可以正常顯示,但是從2016 年 6 月 22 日以後就無法使用了!

<SCRIPT type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false">SCRIPT>
<SCRIPT type="text/javascript">
    function initialize() {
        var rendererOptions = {
            draggable: false
        };
        directionsDisplay = new google.maps.DirectionsRenderer(rendererOptions);
        var center = new google.maps.LatLng(__lat__, __long__);
        var myOptions = {
            zoom:__zoom__,
            mapTypeId: google.maps.MapTypeId.__maptype__,
            center: center
        }
        map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
        var marker = new google.maps.Marker({
            position: center,
        });
        marker.setMap(map);
    }
    $(document.getElementById('map_canvas_div')).ready(function() {
    // do stuff when div is ready
    initialize();
    });
</SCRIPT>

只好改成新的程式碼,而且必須在key後面指定我申請的API金鑰。Google應該會使用這個「AIzaSyBA0p8BaxEcPnIB05kUqxeNjx__6zpkVZ4」奇怪的API金鑰來計算每天顯示的Google地圖的次數。只要超過25,000次的話,就會被Google封鎖地圖顯示功能。

<SCRIPT async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBA0p8BaxEcPnIB05kUqxeNjx__6zpkVZ4&callback=initMap" type="text/javascript"></SCRIPT>

<SCRIPT type="text/javascript">
    function initMap() {
      var gps = {lat: __lat__, lng: __long__};
      var map = new google.maps.Map(document.getElementById('map_canvas'), {
        zoom: __zoom__,
        mapTypeId: google.maps.MapTypeId.__maptype__,
        center: gps
      });
      var marker = new google.maps.Marker({
        position: gps,
        map: map,
      });
    }
</SCRIPT>

 

修正完畢以後,如果拍攝的照片附有GPS地圖資訊,就可以在照片底下看到照片的拍攝地點。就像這樣:

事實上只有更新API金鑰,沒有更新Javascript程式碼內容的話,還會遇到只有Firefox、Google Chrome可以顯示地圖,Safari卻無法顯示的問題。所以我又花了很多時間,想辦法修正Javascript程式碼。

完全不能使用的「exif_read_data」函式

不過,悲慘的事情並不只有Google Maps API定價與方案,PHP內建的EXIF函式「exif_read_data」也有重大的問題!我花了一些時間,找到在2012:09:25用iPhone 4S拍的照片。使用「exif_read_data」函式,可以讀出這樣的EXIF資訊,以及拍攝的GPS資訊。

gExif:

Array
(
    [FileName] => IMG_2617.JPG
    [FileDateTime] => 1505920871
    [FileSize] => 5087232
    [FileType] => 2
    [MimeType] => image/jpeg
    [SectionsFound] => ANY_TAG, IFD0, THUMBNAIL, EXIF, GPS
    [COMPUTED] => Array
        (
            [html] => width="3264" height="2332"
            [Height] => 2332
            [Width] => 3264
            [IsColor] => 1
            [ByteOrderMotorola] => 1
            [ApertureFNumber] => f/2.4
            [Thumbnail.FileType] => 2
            [Thumbnail.MimeType] => image/jpeg
        )

    [Make] => Apple
    [Model] => iPhone 4S
    [Orientation] => 1
    [XResolution] => 72/1
    [YResolution] => 72/1
    [ResolutionUnit] => 2
    [Software] => 6.0
    [DateTime] => 2012:09:25 09:22:56
    [YCbCrPositioning] => 1
    [Exif_IFD_Pointer] => 198
    [GPS_IFD_Pointer] => 580
    [THUMBNAIL] => Array
        (
            [Compression] => 6
            [XResolution] => 72/1
            [YResolution] => 72/1
            [ResolutionUnit] => 2
            [JPEGInterchangeFormat] => 844
            [JPEGInterchangeFormatLength] => 7517
        )

    [ExposureTime] => 1/1842
    [FNumber] => 12/5
    [ExposureProgram] => 2
    [ISOSpeedRatings] => 50
    [ExifVersion] => 0221
    [DateTimeOriginal] => 2012:09:25 09:22:56
    [DateTimeDigitized] => 2012:09:25 09:22:56
    [ComponentsConfiguration] => 
    [ShutterSpeedValue] => 12387/1142
    [ApertureValue] => 4845/1918
    [BrightnessValue] => 12655/1238
    [MeteringMode] => 5
    [Flash] => 0
    [FocalLength] => 107/25
    [FlashPixVersion] => 0100
    [ColorSpace] => 1
    [ExifImageWidth] => 3264
    [ExifImageLength] => 2332
    [SensingMethod] => 2
    [CustomRendered] => 6
    [ExposureMode] => 0
    [WhiteBalance] => 0
    [FocalLengthIn35mmFilm] => 35
    [SceneCaptureType] => 0
    [GPSLatitudeRef] => N
    [GPSLatitude] => Array
        (
            [0] => 31/1
            [1] => 2102/100
            [2] => 0/1
        )

    [GPSLongitudeRef] => E
    [GPSLongitude] => Array
        (
            [0] => 120/1
            [1] => 5853/100
            [2] => 0/1
        )

    [GPSAltitudeRef] => 
    [GPSAltitude] => 6/1
    [GPSTimeStamp] => Array
        (
            [0] => 1/1
            [1] => 22/1
            [2] => 5591/100
        )

)
 

你可以看到在EXIF資訊最後會有GPS的 [GPSLatitude] 與 [GPSLongitude] 的地圖坐標資訊。但是同樣的iPhone 4S智慧電話,在2017:09:05拍攝的照片,只能看到普通的EXIF資訊,無法正確的讀取GPS的 [GPSLatitude] 與 [GPSLongitude] 的資訊!

gExif:

Array
(
    [FileName] => IMG_0039.JPG
    [FileDateTime] => 1505902364
    [FileSize] => 1835373
    [FileType] => 2
    [MimeType] => image/jpeg
    [SectionsFound] => ANY_TAG, IFD0, EXIF
    [COMPUTED] => Array
        (
            [html] => width="3264" height="2448"
            [Height] => 2448
            [Width] => 3264
            [IsColor] => 1
            [ByteOrderMotorola] => 1
            [ApertureFNumber] => f/2.4
        )

    [Make] => Apple
    [Model] => iPhone 4S
    [Orientation] => 1
    [XResolution] => 72/1
    [YResolution] => 72/1
    [ResolutionUnit] => 2
    [Software] => 9.3.5
    [DateTime] => 2017:09:05 16:04:52
    [YCbCrPositioning] => 1
    [ExposureTime] => 1/60
    [FNumber] => 12/5
    [ExposureProgram] => 2
    [ISOSpeedRatings] => 50
    [ExifVersion] => 0221
    [DateTimeOriginal] => 2017:09:05 16:04:52
    [DateTimeDigitized] => 2017:09:05 16:04:52
    [ComponentsConfiguration] => 
    [ShutterSpeedValue] => 4761/806
    [ApertureValue] => 4845/1918
    [BrightnessValue] => 673/151
    [ExposureBiasValue] => 0/1
    [MeteringMode] => 5
    [Flash] => 24
    [FocalLength] => 107/25
    [SubjectLocation] => Array
        (
            [0] => 677
            [1] => 558
            [2] => 221
            [3] => 222
        )

)

 

我以為2017:09:05拍攝的照片被iOS刪除了GPS資訊,所以把那張照片匯入到Photos for macOS,確定照片確實是有附上GPS地圖資訊,但是舊的「exif_read_data」函式只能讀取2012年拍攝的GPS資訊,卻無法正確讀取2017年拍攝的GPS資訊!只能猜想,在2012年用iPhone 4S(當時是iOS 6.1.3版)拍的照片的GPS格式與升級到新的iOS 9.3.5不同,所以才會導致只支援舊版的「exif_read_data」無法讀取iOS 9.3.5以後(甚至可能是iOS 8以後)拍攝的照片的GPS資訊!

我在剛開始設計insoler相簿的EXIF模組的時候,原本只有使用「exif_read_data」函式讀出照片的EXIF內容。但是花了許多時間研究Flickr等相片網站以後,我發現「exif_read_data」函式讀出的內容太過簡陋,沒辦法與Flickr等相片網站相比。所以又花了很多時間尋找可以更完整讀取EXIF資料的模組,終於找到一個叫做ExifTool by Phil Harvey的工具程式。

然後用PHP呼叫外部的ExifTool程式,傳回照片的EXIF參數,來改寫整個EXIF部分,但是我卻一直沒有改寫使用「exif_read_data」函式讀取GPS的部分,所以一直以來都是由ExifTool負責讀取EXIF,由PHP內建函式「exif_read_data」讀取GPS資訊。

由於「exif_read_data」讀取的EXIF太過簡陋,又無法讀取新的照片的GPS資訊,所以只能放棄垃圾等級的「exif_read_data」函式,改用外部的ExifTool程式來讀取EXIF與GPS資訊。這是舊版的程式碼:

        $sExif = shell_exec('/usr/local/bin/exiftool -json ' . $sMediaDir);
        $getEXIF = json_decode($sExif, True);
        $aExif = $getEXIF[0];
        $gExif = @exif_read_data($sMediaDir, 0);
        $iExif = $PhotoEXIF->getPhotoExif($aExif, $gExif);

這是修改好的新版程式碼:

        $sExif = shell_exec('/usr/local/bin/exiftool -c "%.13f degrees" -json ' . $sMediaDir);
        $getEXIF = json_decode($sExif, True);
        $aExif = $getEXIF[0];
        $iExif = $PhotoEXIF->getPhotoExif($aExif);

 

改寫bnw_photo_exif資料表

就算修正好Google地圖,解決老舊「exif_read_data」無法讀取GPS資訊的問題,事實上還有一個相當頭痛的問題!那就是負責儲存EXIF資訊的MySQL Server的bnw_photo_exif資料表過度肥胖!目前的資料表大小已經高達146MB(顯示MiB其實沒有任何意義)。

bnw_photo_exif資料表其實只有2個欄位,一個是照片編號,另外一個則是EXIF陣列的純文字內容。因為剛開始寫EXIF模組的時候,不知道需要多少欄位,而且不同品牌相機的EXIF記錄內容、欄位名稱都不一樣。也就是說EXIF資訊本身並沒有完全統一的標準。所以我把EXIF陣列轉成純文字,就直接儲存在MySQL資料庫中。從MySQL讀取出來以後,再使用「$getEXIF = json_decode($sExif, True);」這樣的解碼指令,把純文字轉成PHP陣列變數。

負責儲存所有照片資訊的「bx_photos_mail」反而只有36.4MB而已!比bnw_photo_exif的146MB小很多!為什麼bnw_photo_exif資料表會這麼大?主要是因為每一筆EXIF資訊,都同時儲存「欄位名稱」以及「資料內容」兩種資料。由於每張照片的「欄位名稱」幾乎是完全相同,卻不斷重複儲存在bnw_photo_exif資料表裡面,所以才會導致浪費了許多空間來儲存相同的「欄位名稱」。

事實上資料庫本來就是只需要儲存「資料」本身即可,不需要儲存「欄位名稱」。但是因為剛開始撰寫EXIF模組的時候,還不知道到底需要多少MySQL資料表欄位,所以我只是簡單的把PHP陣列變數轉成純文字,直接儲存在MySQL。

現在已經可以知道常用的EXIF欄位,所以我打算把老舊的「bnw_photo_exif」資料全部讀取出來,改變成標準的MySQL資料表欄位,放在新的「bx_photo_exif」裡面,確定轉換成功以後,就刪除舊的「bnw_photo_exif」資料表。

由於EXIF資訊需要的資料表欄位比「bx_photos_mail」多很多,所以轉換以後,還是會比「bx_photos_mail」更大,但應該比目前的「bnw_photo_exif」資料表小很多才對,我猜想至少可以縮小50%左右的大小。

另外,從這兩個phpMyAdmin的MySQL資料庫管理畫面,你也可以看到insoler資料庫,已經全部轉換成最新版本的4位元組Unicode的「utf8mb4」格式。

蘇言霖 2017/09/21 0 1138
Comments
Order by: 
Per page:
 
  • There are no comments yet
Rate
0 votes
Post info
蘇言霖
「超級懶貓級」社群網站站長
2017/09/21 (2380 days ago)
Actions