; docformat = 'rst'
;
; NAME:
; cgImage2KML
;
; PURPOSE:
; This program creates a KML file that can be opened in Google Earth to display the
; image drapped over the Google Earth terrain. A corresponding image file is also
; produced. The KML and image file must be in the same directory to use them with
; Google Earth.
;
;******************************************************************************************;
; ;
; Copyright (c) 2012, by Fanning Software Consulting, Inc. All rights reserved. ;
; ;
; Redistribution and use in source and binary forms, with or without ;
; modification, are permitted provided that the following conditions are met: ;
; ;
; * Redistributions of source code must retain the above copyright ;
; notice, this list of conditions and the following disclaimer. ;
; * Redistributions in binary form must reproduce the above copyright ;
; notice, this list of conditions and the following disclaimer in the ;
; documentation and/or other materials provided with the distribution. ;
; * Neither the name of Fanning Software Consulting, Inc. nor the names of its ;
; contributors may be used to endorse or promote products derived from this ;
; software without specific prior written permission. ;
; ;
; THIS SOFTWARE IS PROVIDED BY FANNING SOFTWARE CONSULTING, INC. ''AS IS'' AND ANY ;
; EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ;
; OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT ;
; SHALL FANNING SOFTWARE CONSULTING, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, ;
; INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED ;
; TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; ;
; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ;
; ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ;
; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ;
; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ;
;******************************************************************************************;
;
;+
; This program creates a KML file that can be opened in Google Earth to display the
; image drapped over the Google Earth terrain. A corresponding image file is also
; produced. The KML and image file must be in the same directory to use them with
; Google Earth.
;
; .. image:: cgimage2kml.png
;
; :Categories:
; Graphics, FileIO, Maps
;
; :Params:
;
; image: in, optional
; A 2D image or a 24-bit image with or without an alpha channel. If an alpha
; channel is present, it will be modified by the program if the `Transparent`
; keyword is used. An image is required unless the `GeoTiff` keyword is used
; to obtain an image.
;
; mapcoord: in, optional, type=object
; A map coordinate object (cgMap) from which map projection information and map
; boundaries for the image overlay can be obtained. This parameter is required
; unless the `GeoTiff` keyword is used to obtain a map coordinate object.
;
; :Keywords:
;
; addtofile: in, optional, type=object
; If this keyword contains a cgKML_File object, the image is added to the file
; as a ' and set up the path
; to 7z.exe correctly in cgKML2KMZ. If you don't understand, please don't
; set this keyword!
;
; latlonbox: out, optional, type=array
; A four-element array giving the boundaries of the map projection in the
; Google Map form of [north, south, east, west]. Normally, this information
; is obtained from the mapCoord object and need not be passed in. The values
; are in latitude and longitude coordinates that go from -90 to 90 and -180 to
; 180 degrees, respectively.
;
; max_value: in, optional
; The value to use for the MAX value when the image is scaled with BYTSCL.
;
; min_value: in, optional
; The value to use for the MIN value when the image is scaled with BYTSCL.
;
; missing_value: in, optional, type=various
; The "color" of a pixel that will be treated as a "missing" color or value.
; Any pixels in the image with this color value will be set completely
; transparent. If `Color` is a string, use cgColor to obtain a color triple.
; If `Color` is a non-strint scalar, this value is taken to be the missing color index
; in a 2D image. Otherwise, this is assumed to be a color triple that indicates
; the "missing" color or value in the output image. The alpha channel in the output image
; is set to 0 for the "missing" color, which makes this value completely transparent.
; If the `Transparent` keyword is not used, it is set to 0 by using the `Missing_Value`
; keyword.
;
; palette: in, optional, type=byte
; Set this keyword to a 3x256 or 256x3 byte array containing the RGB color
; vectors to be loaded before the transparent image is created. Such vectors can be
; obtained, for example, from cgLoadCT with the RGB_TABLE keyword::
;
; IDL> cgLoadCT, 4, /BREWER, /REVERSE, RGB_TABLE=palette
; IDL> tImage = cgTransparentImage( cgDemoData(7), PALETTE=palette)
;
; The default is to use whatever colors are loaded in the current hardware color table.
; A palette applies only to 2D input images.
;
; placename: in, optional, type=string
; This is the element in a Feature object. It is user-defined text that is used as
; the label for an object in Google Earth.
;
; resize_factor: in, optional, type=float
; Setting this keyword to a value allows the user to resize the image prior to making the
; PNG image file that will be output with the KML file. This is especially helpful with
; very large images. Setting the factor to 0.5 will reduce the image to half it's normal
; size before processing. Setting the factor to 2.0 will increase the size by a factor
; of 2 before processing. The image is resized with nearest neighbor sampling.
;
; reverse: in, optional, type=boolean, default=0
; Set this keyword to reverse the color table vectors selected with the `CTIndex` keyword.
;
; transparent: in, optional, type=integer, default=50
; The percentage of transparency desired in the output image. A number
; between 0 and 100.
;
; :Examples:
; Here is how you can put an AVHRR NDVI image of Africa on a Google Earth display::
;
; ;; Download the image file from the Coyote web page.
; netObject = Obj_New('IDLnetURL')
; url = 'http://www.idlcoyote.com/data/AF03sep15b.n16-VIg.tif'
; returnName = netObject -> Get(URL=url, FILENAME='AF03sep15b.n16-VIg.tif')
; Obj_Destroy, netObject
;
; ;; Create the image overlay KML file.
; cgImage2KML, GeoTiff='AF03sep15b.n16-VIg.tif', Min_Value=0, CTIndex=11, $
; /Brewer, /Reverse, Transparent=50, Filename='avhrr_ndvi.kml', $
; Description='AVHRR NDVI Data from Africa'
;
; ;; Start Google Earth and open the KML file you just created.
;
; The output should look like the figure above.
;
; :Author:
; FANNING SOFTWARE CONSULTING::
; David W. Fanning
; 1645 Sheely Drive
; Fort Collins, CO 80526 USA
; Phone: 970-221-0438
; E-mail: david@idlcoyote.com
; Coyote's Guide to IDL Programming: http://www.idlcoyote.com
;
; :History:
; Change History::
; Written, 30 October 2012 by David W. Fanning.
; Added DRAWORDER keyword and fixed a typo concerning MISSING_VALUE. 31 Oct 2012. DWF.
; Fixed a problem that was causing floating underflow warnings to be thrown. 5 Nov 2012. DWF.
; Images with values between 0 and 255 were not getting scaled properly. Fixed. 30 Nov 2012. DWF.
; Added a FlyTo keyword to allow the user to fly to a particular location on the Earth. 31 Dec 2012. DWF.
; Was not handling 24- or 32-bit images correctly, nor was the MISSING_COLOR keyword being
; interpreted correctly when expressed as a color string. 20 Feb 2013. DWF.
; Have been writing the absolute path to the image file into the KML file, when I should
; have been using a relative path. 22 Feb 2013. DWF.
; Problem with the MISSING keyword. Fixed. 14 Mar 2013. DWF.
;
; :Copyright:
; Copyright (c) 2012, Fanning Software Consulting, Inc.
;-
PRO cgImage2KML, image, mapCoord, $
ADDTOFILE=addtofile, $
BREWER=brewer, $
CTINDEX=ctindex, $
DESCRIPTION=description, $
DRAWORDER=draworder, $
GEOTIFF=geotiff, $
FILENAME=filename, $
FLYTO=flyto, $
KMZ=kmz, $
LATLONBOX=latlonbox, $
MAX_VALUE=max_value, $
MIN_VALUE=min_value, $
MISSING_VALUE=missing_value, $
PALETTE=palette, $
PLACENAME=placename, $
RESIZE_FACTOR=resize_factor, $
REVERSE=reverse, $
TRANSPARENT=transparent
Compile_Opt idl2
; Error handling.
Catch, theError
IF theError NE 0 THEN BEGIN
Catch, /CANCEL
void = cgErrorMsg()
RETURN
ENDIF
; If the KMZ keyword is set, make sure this version of IDL supports it.
IF Keyword_Set(kmz) THEN BEGIN
IF Float(!Version.Release) LT 8.0 THEN BEGIN
Message, 'The KMZ keyword is not supported in this version of IDL.', /Informational
kmz = 0
ENDIF
ENDIF
; If the user sets either the MIN_VALUE or MAX_VALUE keyword, then you should scale the image.
IF (N_Elements(min_value) GT 0) || (N_Elements(max_value) GT 0) THEN scaleit = 1 ELSE scaleit = 0
; Has the GEOTIFF keyword been used to obtain program variables?
IF N_Elements(geotiff) NE 0 THEN BEGIN
mapCoord = cgGeoMap(geotiff, $
BOUNDARY=boundary, $
ELLIPSOID=ellipsoid, $
IMAGE=image, $
LATLONBOX=latlonbox, $
MAP_PROJECTION=map_projection, $
PALETTE=palette)
ENDIF
; Check required parameters.
IF N_Elements(image) EQ 0 THEN Message, 'An image parameter is required to proceed.'
IF N_Elements(mapCoord) EQ 0 THEN BEGIN
IF N_Elements(latlonBox) EQ 0 THEN Message, 'A map coordinate object is required to proceed.'
ENDIF ELSE BEGIN
mapCoord -> GetProperty, $
BOUNDARY=boundary, $
ELLIPSOID=ellipsoid, $
LATLONBOX=latlonbox, $
MAP_PROJECTION=map_projection
ENDELSE
IF N_Elements(latlonBox) EQ 0 THEN Message, 'Map boundaries for the image cannot be obtained.'
IF N_Elements(order) EQ 0 THEN order = 0
IF (N_Elements(missing_value) NE 0) && (N_Elements(transparent) EQ 0) THEN BEGIN
transparent = 0
ENDIF
; Handle the flyTo values.
IF N_Elements(flyTo) NE 0 THEN BEGIN
IF N_Elements(flyTo) LT 2 THEN Message, 'FlyTo keyword should be 2- or 3-element array.'
IF N_Elements(flyTo) EQ 2 THEN flyTo = [flyTo, 11000]
IF N_Elements(flyTo) GT 3 THEN Message, 'FlyTo keyword should be 2- or 3-element array.'
lookAtObj = Obj_New('cgKML_LookAt', LONGITUDE=flyTo[0], LATITUDE=flyTo[1], HEIGHT=flyTo[2])
ENDIF
; Need a filename?
IF N_Elements(filename) EQ 0 THEN BEGIN
CD, CURRENT=thisDir
filename = Filepath(ROOT_DIR=thisDir, 'kml_image.kml')
ENDIF
; Construct the image filename
rootName = cgRootName(filename, DIRECTORY=thisDir, EXTENSION=ext)
IF StrUpCase(ext) NE 'KML' THEN Message, 'The output filename must have a KML file extension.'
imageFilename = Filepath(ROOT_DIR=thisDir, rootname + '.png')
; If you got this far, and you don't have a map coordinate object, you will have
; to create one.
IF N_Elements(mapCoord) EQ 0 THEN BEGIN
mapCoord = Obj_New('cgMap', 'Equirectangular', Ellipsoid='WGS 84', $
XRange=[latlonbox[2], latlonbox[3]], YRange=[latlonBox[1], latlonbox[0]], /LATLON_RANGES)
mapCoord -> GetProperty, $
BOUNDARY=boundary, $
ELLIPSOID=ellipsoid, $
MAP_PROJECTION=map_projection
ENDIF
; Are you loading a color table?
IF N_Elements(ctindex) NE 0 THEN BEGIN
cgLoadCT, ctindex, BREWER=brewer, REVERSE=reverse, RGB_TABLE=palette
ENDIF
; Otherwise, let's use gray-scale colors for the image.
IF N_Elements(palette) EQ 0 THEN cgLoadCT, 0, RGB_TABLE=palette
; How many dimensions does this image have?
ndims = Size(image, /N_Dimensions)
; Should the image be reduced in size before displaying it?
IF N_Elements(resize_factor) NE 0 THEN BEGIN
dims = Image_Dimensions(image, XSIZE=xsize, YSIZE=ysize)
warped = cgResizeImage(image, Long(xsize*resize_factor), Long(ysize*resize_factor))
ENDIF ELSE warped = image
; If the map projection is not "EQUIRECTANGULAR" or the ellipsoid is not "WGS84", then the
; image has to be warped into the correct map projection.
IF (StrUpCase(map_projection) NE 'EQUIRECTANGULAR') || (StrUpCase(StrCompress(ellipsoid, /REMOVE_ALL)) NE 'WGS84') THEN BEGIN
googleMapCoord = Obj_New('cgMap', 'Equirectangular', Ellipsoid='WGS 84')
ndims = Size(warped, /N_Dimensions)
warped = cgChangeMapProjection(warped, mapCoord, MAPOUT=googleMapCoord, $
LATLONBOX=latlonbox, MISSING=thisMissingValue)
ENDIF
; Byte scale the image.
IF (N_Elements(missing_value) NE 0) THEN BEGIN
IF ndims EQ 2 THEN BEGIN
imgType = Size(warped, /TNAME)
IF (imgType NE 'FLOAT') && (imgType NE 'DOUBLE') THEN warped = Float(warped)
missing = Where(warped EQ missing_value, count)
IF count GT 0 THEN warped[missing] = !Values.F_NAN
IF (Min(warped, /NAN) LT 0) || (Max(warped, /NAN) GT 255) || scaleIt THEN BEGIN
warped = BytScl(warped, MIN=min_value, MAX=max_value, /NAN, TOP=254) + 1B
ENDIF
IF count GT 0 THEN warped[missing] = 0B
ENDIF
ENDIF ELSE BEGIN
IF (Min(warped) LT 0) || (Max(warped) GT 255) || scaleIt THEN BEGIN
warped = BytScl(warped, MIN=min_value, MAX=max_value, /NAN)
ENDIF
ENDELSE
; If this is a 2D image, create a 24-bit image from the palette.
r = Reform(palette[*,0])
g = Reform(palette[*,1])
b = Reform(palette[*,2])
IF (ndims EQ 2) THEN warped = [ [[r[warped]]], [[g[warped]]], [[b[warped]]]]
; Do we need transparency?
IF N_Elements(transparent) NE 0 THEN BEGIN
IF (ndims EQ 2) THEN BEGIN
warped = cgTransparentImage(warped, TRANSPARENT=transparent, MISSING_VALUE=[r[0],g[0],b[0]])
ENDIF ELSE BEGIN
warped = cgTransparentImage(warped, TRANSPARENT=transparent, MISSING_VALUE=missing_value)
ENDELSE
ENDIF
; Save the image as a PNG file.
dims = Image_Dimensions(warped, XINDEX=xindex, YINDEX=yindex, TRUEINDEX=trueindex)
IF trueindex GT 0 THEN warped = Transpose(warped, [trueindex, xindex, yindex])
IF cgObj_Isa(addtofile, 'cgKML_File') THEN BEGIN
addToFile -> GetProperty, FILENAME=filename, COUNT=count
rootname = cgRootName(filename, DIRECTORY=thisDir, EXTENSION=ext)
imageFilename = FilePath(ROOT_DIR=thisDir, rootname + StrTrim(count+1,2) + '.png')
; Write the image file.
Write_PNG, imageFilename, warped
overlay = Obj_New('cgKML_GroundOverlay', $
HREF=File_BaseName(imageFilename), $
DESCRIPTION=description, $
LATLONBOX=latlonBox, $
PLACENAME=placename, $
DRAWORDER=draworder)
addToFile -> Add, overlay
; Do you have a lookAt object?
IF Obj_Valid(lookAtObj) THEN addToFile -> Add, lookAtObj
ENDIF ELSE BEGIN
; Write the image file.
Write_PNG, imageFilename, warped
; Write the KML file.
kmlFile = Obj_New('cgKML_File', filename)
overlay = Obj_New('cgKML_GroundOverlay', $
HREF=File_BaseName(imageFilename), $
DESCRIPTION=description, $
LATLONBOX=latlonBox, $
PLACENAME=placename, $
DRAWORDER=draworder)
kmlFile -> Add, overlay
; Do you have a lookAt object?
IF Obj_Valid(lookAtObj) THEN kmlFile -> Add, lookAtObj
kmlFile -> Save, KMZ=Keyword_Set(kmz)
kmlFile -> Destroy
ENDELSE
END