[mapserver-commits] r12630 - trunk/mapserver

svn at osgeo.org svn at osgeo.org
Thu Oct 6 14:33:38 EDT 2011


Author: pramsey
Date: 2011-10-06 11:33:38 -0700 (Thu, 06 Oct 2011)
New Revision: 12630

Added:
   trunk/mapserver/mapxmp.c
Modified:
   trunk/mapserver/HISTORY.TXT
   trunk/mapserver/Makefile.in
   trunk/mapserver/README.CONFIGURE
   trunk/mapserver/configure.in
   trunk/mapserver/maperror.c
   trunk/mapserver/mapgdal.c
   trunk/mapserver/mapserver.h
Log:
Support for XMP metadata in output images (#3932) RFC 76


Modified: trunk/mapserver/HISTORY.TXT
===================================================================
--- trunk/mapserver/HISTORY.TXT	2011-10-06 18:06:34 UTC (rev 12629)
+++ trunk/mapserver/HISTORY.TXT	2011-10-06 18:33:38 UTC (rev 12630)
@@ -15,6 +15,7 @@
 Current Version (SVN trunk, 6.1-dev, future 6.2): 
 -------------------------------------------------
 
+- Added XMP support for metadata embedding, RFC 76, (#3932)
 - Added GetLegendGraphic Cascading support (#3923)
 
 - Rewrite  postgres TIME queries to take advantage of indexes (#3374)

Modified: trunk/mapserver/Makefile.in
===================================================================
--- trunk/mapserver/Makefile.in	2011-10-06 18:06:34 UTC (rev 12629)
+++ trunk/mapserver/Makefile.in	2011-10-06 18:33:38 UTC (rev 12630)
@@ -177,8 +177,11 @@
 EXSLT_INC=@EXSLT_INC@
 EXSLT_LIB=@EXSLT_LIB@
 
+# Optional Exempi
+EXEMPI=@EXEMPI_ENABLED@
+EXEMPI_INC=@EXEMPI_INC@
+EXEMPI_LIB=@EXEMPI_LIB@
 
-
 # Optional FastCGI
 FASTCGI=@FASTCGI_ENABLED@
 FASTCGI_INC=@FASTCGI_INC@
@@ -247,7 +250,7 @@
         $(AGG_INC) $(OGL_INC) $(FTGL_INC) $(PROJ_INC) $(EGIS_INC) \
         $(SDE_INC) $(GDAL_INC) $(POSTGIS_INC) $(MYSQL_INC) \
         $(CURL_INC) $(ORACLESPATIAL_INC) $(GEOS_INC) $(ICONV_INC) \
-        $(FASTCGI_INC) $(ZLIB_INC) $(XML2_INC) $(FRIBIDI_INC) $(CAIRO_INC)
+        $(FASTCGI_INC) $(EXEMPI_INC) $(ZLIB_INC) $(XML2_INC) $(FRIBIDI_INC) $(CAIRO_INC)
 
 FLAGS = @DEBUG_FLAGS@ $(DEFINES) $(INCLUDES)
 
@@ -258,7 +261,7 @@
 SUP_LIBS =  $(FT_LIB) $(GD_LIB) $(AGG_LIB) $(OGL_LIB) $(FTGL_LIB) $(PROJ_LIBS) \
           $(JPEG_LIB) $(PNG_LIB) $(GIF_LIB) $(SDE_LIB) $(GDAL_LIB) $(POSTGIS_LIB) \
 	  $(MYSQL_LIB) $(CURL_LIB) $(ORACLESPATIAL_LIB) $(GEOS_LIB) \
-	  $(THREAD_LIB) $(ICONV_LIB) $(FASTCGI_LIB) $(XSLT_LIB) $(EXSLT_LIB) \
+	  $(THREAD_LIB) $(ICONV_LIB) $(FASTCGI_LIB) $(EXEMPI_LIB) $(XSLT_LIB) $(EXSLT_LIB) \
 	  $(ZLIB_LIB) $(XML2_LIB) $(FRIBIDI_LIB) $(XTRALIBS)  $(CAIRO_LIB)
 
 # STATIC_LIBS is full filename with path of libs that will be statically linked
@@ -281,7 +284,7 @@
 				maprasterquery.o mapobject.o mapgeos.o classobject.o layerobject.o mapio.o mappool.o \
 				mapregex.o mappluginlayer.o mapogcsos.o mappostgresql.o mapcrypto.o mapowscommon.o \
 				maplibxml2.o mapdebug.o mapchart.o maptclutf.o mapxml.o mapkml.o mapkmlrenderer.o \
-				mapogroutput.o mapwcs20.o  mapogcfiltercommon.o mapunion.o mapcluster.o
+				mapogroutput.o mapwcs20.o  mapogcfiltercommon.o mapunion.o mapcluster.o mapxmp.o
 
 EXE_LIST = 	shp2img legend mapserv shptree shptreevis \
 		shptreetst scalebar sortshp mapscriptvars tile4ms \

Modified: trunk/mapserver/README.CONFIGURE
===================================================================
--- trunk/mapserver/README.CONFIGURE	2011-10-06 18:06:34 UTC (rev 12629)
+++ trunk/mapserver/README.CONFIGURE	2011-10-06 18:33:38 UTC (rev 12630)
@@ -303,10 +303,23 @@
 - GEOS support is optional, and is not included by default.
   MapServer requires GEOS version 2.2.3 or more recent.
 
-- The GEOS library can be found at http://geos.refractions.net/
+- The GEOS library can be found at http://trac.osgeo.org/geos/
 
 
+XMP Support:
+------------
 
+::
+
+  --with-exempi=PATH
+
+- XMP support is optional, and is not incuded by default.
+
+- XMP support allows metadata (title, attribute, license, etc) to be directly embedded in the images generated by MapServer.
+
+- The exempi library can be found at http://libopenraw.freedesktop.org/wiki/Exempi
+
+
 OGR Support:
 ------------
 

Modified: trunk/mapserver/configure.in
===================================================================
--- trunk/mapserver/configure.in	2011-10-06 18:06:34 UTC (rev 12629)
+++ trunk/mapserver/configure.in	2011-10-06 18:33:38 UTC (rev 12630)
@@ -1942,6 +1942,44 @@
 fi
 
 dnl ---------------------------------------------------------------------
+dnl Exempi support
+dnl ---------------------------------------------------------------------
+
+AC_ARG_WITH(exempi,
+[  --with-exempi=DIR Specify path to exempi.],,)
+
+if test "$with_exempi" != "no" ; then
+
+  if test "$with_exempi" = "yes" ; then
+    AC_CHECK_HEADER([xmp.h],,[AC_MSG_ERROR([cannot find xmp.h])])
+    EXEMPI_INC=""
+    EXEMPI_LIB="-lexempi"
+  else
+    xmp_dir=$with_exempi
+    xmp_include=$xmp_dir/include
+    xmp_lib=$xmp_dir/lib
+    xmp_found=
+    for xmp_subdir in / /exempi /exempi-2.0/exempi; do
+      AC_CHECK_HEADER([$xmp_include$xmp_subdir/xmp.h],xmp_found=$xmp_subdir,,)
+    done
+    if test "x$xmp_found" = "x" ; then
+      AC_MSG_ERROR([cannot find xmp.h in $xmp_include])
+    else
+      EXEMPI_INC="-I$xmp_include$xmp_subdir"
+    fi
+    EXEMPI_LIB="-L$xmp_lib -lexempi"
+  fi
+
+  AC_CHECK_LIB(exempi,xmp_init,EXEMPI_ENABLED=-DUSE_EXEMPI,,$EXEMPI_LIB)
+
+fi
+
+AC_SUBST(EXEMPI_ENABLED,$EXEMPI_ENABLED)
+AC_SUBST(EXEMPI_INC,    $EXEMPI_INC)
+AC_SUBST(EXEMPI_LIB,    $EXEMPI_LIB)
+ALL_ENABLED="$EXEMPI_ENABLED $ALL_ENABLED"
+
+dnl ---------------------------------------------------------------------
 dnl Look for libxml2 if SOS Server requested
 dnl ---------------------------------------------------------------------
 
@@ -2866,6 +2904,7 @@
 AC_MSG_RESULT([  FriBidi support:           ${FRIBIDI_ENABLED}])
 AC_MSG_RESULT([  Curl support:              ${CURL_ENABLED}])
 AC_MSG_RESULT([  FastCGI support:           ${FASTCGI_ENABLED}])
+AC_MSG_RESULT([  Exempi support:            ${EXEMPI_ENABLED}])
 AC_MSG_RESULT([  Threading support:         ${THREAD_FLAG}])
 AC_MSG_RESULT([  GEOS support:              ${GEOS_ENABLED}])
 AC_MSG_RESULT([  XML Mapfile support:        ${XMLMAPFILE_ENABLED}])

Modified: trunk/mapserver/maperror.c
===================================================================
--- trunk/mapserver/maperror.c	2011-10-06 18:06:34 UTC (rev 12629)
+++ trunk/mapserver/maperror.c	2011-10-06 18:33:38 UTC (rev 12630)
@@ -538,6 +538,9 @@
 #ifdef USE_ICONV
   strcat(version, " SUPPORTS=ICONV");
 #endif
+#ifdef USE_EXEMPI
+  strcat(version, " SUPPORTS=XMP");
+#endif
 #ifdef USE_FRIBIDI
   strcat(version, " SUPPORTS=FRIBIDI");
 #endif

Modified: trunk/mapserver/mapgdal.c
===================================================================
--- trunk/mapserver/mapgdal.c	2011-10-06 18:06:34 UTC (rev 12629)
+++ trunk/mapserver/mapgdal.c	2011-10-06 18:33:38 UTC (rev 12630)
@@ -162,10 +162,16 @@
     outputFormatObj *format = image->format;
     rasterBufferObj rb;
     GDALDataType eDataType = GDT_Byte;
+    int bUseXmp = MS_FALSE;
 
     msGDALInitialize();
     memset(&rb,0,sizeof(rasterBufferObj));
+    
+#ifdef USE_EXEMPI
+    bUseXmp = msXmpPresent(map);
+#endif        
 
+
 /* -------------------------------------------------------------------- */
 /*      Identify the proposed output driver.                            */
 /* -------------------------------------------------------------------- */
@@ -193,7 +199,7 @@
             pszExtension = "img.tmp";
 
 #ifdef GDAL_DCAP_VIRTUALIO
-        if( GDALGetMetadataItem( hOutputDriver, GDAL_DCAP_VIRTUALIO, NULL ) 
+        if( bUseXmp == MS_FALSE && GDALGetMetadataItem( hOutputDriver, GDAL_DCAP_VIRTUALIO, NULL ) 
             != NULL )
         {
             CleanVSIDir( "/vsimem/msout" );
@@ -527,10 +533,27 @@
     GDALClose( hMemDS );
 
     GDALClose( hOutputDS );
-
     msReleaseLock( TLOCK_GDAL );
 
+
 /* -------------------------------------------------------------------- */
+/*      Are we writing license info into the image?                     */
+/*      If so, add it to the temp file on disk now.                     */
+/* -------------------------------------------------------------------- */
+#ifdef USE_EXEMPI
+    if ( bUseXmp == MS_TRUE )
+    {
+        if( msXmpWrite(map, filename) == MS_FAILURE )
+        {
+            /* Something bad happened. */
+            msSetError( MS_MISCERR, "XMP write to %s failed.\n%s",
+                        "msSaveImageGDAL()", filename);
+            return MS_FAILURE;
+        }
+    }
+#endif
+
+/* -------------------------------------------------------------------- */
 /*      Is this supposed to be a temporary file?  If so, stream to      */
 /*      stdout and delete the file.                                     */
 /* -------------------------------------------------------------------- */

Modified: trunk/mapserver/mapserver.h
===================================================================
--- trunk/mapserver/mapserver.h	2011-10-06 18:06:34 UTC (rev 12629)
+++ trunk/mapserver/mapserver.h	2011-10-06 18:33:38 UTC (rev 12630)
@@ -2479,8 +2479,12 @@
 MS_DLL_EXPORT void msHexEncode(const unsigned char *in, char *out, int numbytes);
 MS_DLL_EXPORT int msHexDecode(const char *in, unsigned char *out, int numchars);
 
+/* ==================================================================== */
+/*      prototypes for functions in mapxmp.c                            */
+/* ==================================================================== */
+MS_DLL_EXPORT int msXmpPresent(mapObj *map);
+MS_DLL_EXPORT int msXmpWrite(mapObj *map, const char *filename);
 
-
 /* ==================================================================== */
 /*      prototypes for functions in mapgeomtransform.c                  */
 /* ==================================================================== */

Added: trunk/mapserver/mapxmp.c
===================================================================
--- trunk/mapserver/mapxmp.c	                        (rev 0)
+++ trunk/mapserver/mapxmp.c	2011-10-06 18:33:38 UTC (rev 12630)
@@ -0,0 +1,304 @@
+/******************************************************************************
+ * $Id: mapchart.c 11880 2011-07-07 19:51:37Z sdlime $
+ *
+ * Project:  MapServer
+ * Purpose:  XMP embedded image metadata (MS-RFC-7X)
+ * Author:   Paul Ramsey <pramsey at opengeo.org>
+ *
+ ******************************************************************************
+ * Copyright (c) 1996-2007 Regents of the University of Minnesota.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in 
+ * all copies of this Software or works derived from this Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+#include "mapserver.h"
+
+
+#ifdef USE_EXEMPI
+
+/* To pull parts out of hash keys */
+#include <regex.h>
+
+/* To write the XMP XML into the files */
+#include <xmp.h>
+#include <xmpconsts.h>
+
+/**
+* Get standard Exempi namespace URI for a given namespace string
+*/
+static const char* 
+msXmpUri(char *ns_name)
+{
+    /* Creative Commons */
+    if( strcmp(ns_name, "cc") == 0 )
+        return NS_CC;
+    /* Dublin Core */
+    else if( strcmp(ns_name, "dc") == 0 )
+        return NS_DC;
+    /* XMP Meta */
+    else if( strcmp(ns_name, "meta") == 0 )
+        return NS_XMP_META;
+    /* XMP Rights */
+    else if( strcmp(ns_name, "rights") == 0 )
+        return NS_XAP_RIGHTS;
+    /* EXIF */
+    else if( strcmp(ns_name, "exif") == 0 )
+        return NS_EXIF;
+    /* TIFF */
+    else if( strcmp(ns_name, "tiff") == 0 )
+        return NS_TIFF;
+    /* Photoshop Camera Raw Schema */
+    else if( strcmp(ns_name, "crs") == 0 )
+        return NS_CAMERA_RAW_SETTINGS;
+    /* Photoshop */
+    else if( strcmp(ns_name, "photoshop") == 0 )
+        return NS_PHOTOSHOP;
+    else 
+        return NULL;
+}
+
+#endif
+
+/**
+* Is there any XMP metadata in the map file for us to worry about?
+*/
+int 
+msXmpPresent( mapObj *map )
+{
+#ifdef USE_EXEMPI
+
+    /* Read the WEB.METADATA */
+    hashTableObj hash_metadata = map->web.metadata;
+    const char *key = NULL;
+    int rv = MS_FALSE;
+    
+    /* Check all the keys for "xmp_" start pattern */
+    key = msFirstKeyFromHashTable(&hash_metadata);
+    do 
+    {
+        /* Found one! Break out and return true */
+        if ( strcasestr(key, "xmp_") == key )
+        {
+            rv = MS_TRUE;
+            break;
+        }
+    }
+    while( (key = msNextKeyFromHashTable(&hash_metadata, key)) );
+    
+    return rv;
+    
+#else
+    return MS_FALSE;
+#endif
+}
+
+
+/**
+* Is there any XMP metadata in the map file for us to worry about?
+*/
+int 
+msXmpWrite( mapObj *map, const char *filename )
+{
+#ifdef USE_EXEMPI
+
+    /* Should hold our keys */
+    hashTableObj hash_metadata = map->web.metadata;
+    /* Temporary place for custom name spaces */
+    hashTableObj hash_ns;
+    /* We use regex to strip out the namespace and XMP key value from the metadata key */
+    regex_t xmp_regex;
+    const char *xmp_ns_str = "^xmp_(.+)_namespace$";
+    const char *xmp_tag_str = "^xmp_(.+)_(.+)$";
+    const char *key = NULL;
+    static int regflags = REG_ICASE | REG_EXTENDED;
+
+    /* XMP object and file pointers */
+    XmpPtr xmp;
+    XmpFilePtr f;
+
+    /* Force the hash table to empty */
+    hash_ns.numitems = 0;
+    
+    /* Prepare XMP library */
+    xmp_init();
+    f = xmp_files_open_new(filename, XMP_OPEN_FORUPDATE);
+    if ( ! f ) 
+    {
+        msSetError( MS_MISCERR, 
+                    "Unable to open temporary file '%s' to write XMP info",
+                    "msXmpWrite()", filename );
+        return MS_FAILURE;
+    }
+
+    /* Create a new XMP structure if the file doesn't already have one */
+    xmp = xmp_files_get_new_xmp(f);
+    if ( xmp == NULL ) 
+        xmp = xmp_new_empty();
+
+    /* Check we can write to the file */
+    if ( ! xmp_files_can_put_xmp(f, xmp) ) 
+    {
+        msSetError( MS_MISCERR, 
+                    "Unable to write XMP information to '%s'",
+                    "msXmpWrite()", filename );
+        return MS_FAILURE;
+    }
+    
+    /* Compile our "xmp_*_namespace" regex */
+    if ( regcomp(&xmp_regex, xmp_ns_str, regflags) )
+    {
+        msSetError( MS_MISCERR, 
+                    "Unable compile regex '%s'",
+                    "msXmpWrite()", xmp_ns_str );
+        return MS_FAILURE;
+    }
+
+    /* Check all the keys for "xmp_*_namespace" pattern */
+    initHashTable(&hash_ns);
+    key = msFirstKeyFromHashTable(&hash_metadata);
+    do 
+    {
+        /* Our regex has one match slot */
+        regmatch_t matches[2];
+
+        /* Found a custom namespace entry! Store it for later. */
+        if ( 0 == regexec(&xmp_regex, key, 2, matches, 0) )
+        {
+            size_t ns_size = 0;
+            char *ns_name = NULL;
+            const char *ns_uri;
+
+            /* Copy in the namespace name */
+            ns_size = matches[1].rm_eo - matches[1].rm_so;
+            ns_name = msSmallMalloc(ns_size + 1);
+            memcpy(ns_name, key + matches[1].rm_so, ns_size);
+            ns_name[ns_size] = 0; /* null terminate */
+
+            /* Copy in the namespace uri */
+            ns_uri = msLookupHashTable(&hash_metadata, key);
+            msInsertHashTable(&hash_ns, ns_name, ns_uri);
+            xmp_register_namespace(ns_uri, ns_name, NULL);
+            msFree(ns_name);
+        }
+    } 
+    while( (key = msNextKeyFromHashTable(&hash_metadata, key)) );
+    /* Clean up regex */
+    regfree(&xmp_regex);
+
+
+    /* Compile our "xmp_*_*" regex */
+    if ( regcomp(&xmp_regex, xmp_tag_str, regflags) )
+    {
+        msFreeHashItems(&hash_ns);
+        msSetError( MS_MISCERR, 
+                    "Unable compile regex '%s'",
+                    "msXmpWrite()", xmp_tag_str );
+        return MS_FAILURE;
+    }
+
+    /* Check all the keys for "xmp_*_*" pattern */
+    key = msFirstKeyFromHashTable(&hash_metadata);
+    do 
+    {
+        /* Our regex has two match slots */
+        regmatch_t matches[3];
+
+        /* Found a namespace entry! Write it into XMP. */
+        if ( 0 == regexec(&xmp_regex, key, 3, matches, 0) )
+        {
+            /* Get the namespace and tag name */
+            size_t ns_name_size = matches[1].rm_eo - matches[1].rm_so;
+            size_t ns_tag_size = matches[2].rm_eo - matches[2].rm_so;
+            char *ns_name = msSmallMalloc(ns_name_size + 1);
+            char *ns_tag = msSmallMalloc(ns_tag_size + 1);
+            const char *ns_uri = NULL;
+            memcpy(ns_name, key + matches[1].rm_so, ns_name_size);
+            memcpy(ns_tag, key + matches[2].rm_so, ns_tag_size);
+            ns_name[ns_name_size] = 0; /* null terminate */
+            ns_tag[ns_tag_size] = 0; /* null terminate */
+
+            if ( strcmp(ns_tag,"namespace") == 0 )
+            {
+                msFree(ns_name);
+                msFree(ns_tag);
+                continue;
+            }
+            
+            /* If this is a default name space?... */
+            if ( (ns_uri = msXmpUri(ns_name)) )
+            {
+                xmp_register_namespace(ns_uri, ns_name, NULL);
+                xmp_set_property(xmp, ns_uri, ns_tag, msLookupHashTable(&hash_metadata, key), 0);
+            }
+            /* Or maybe it's a custom one?... */
+            else if ( (ns_uri = msLookupHashTable(&hash_ns, ns_name)) )
+            {
+                xmp_set_property(xmp, ns_uri, ns_tag, msLookupHashTable(&hash_metadata, key), 0);
+            }
+            /* Or perhaps we're screwed. */
+            else
+            {
+                msFreeHashItems(&hash_ns);
+                msFree(ns_name);
+                msFree(ns_tag);
+                msSetError( MS_MISCERR, 
+                            "Unable to identify XMP namespace '%s' in metadata key '%s'",
+                            "msXmpWrite()", ns_name, key );
+                return MS_FAILURE;
+            }
+            msFree(ns_name);
+            msFree(ns_tag);            
+        }
+    }
+    while( (key = msNextKeyFromHashTable(&hash_metadata, key)) );
+
+    /* Clean up regex */
+    regfree(&xmp_regex);
+        
+    /* Write out the XMP */
+    if ( !xmp_files_put_xmp(f, xmp) )
+    {
+        msFreeHashItems(&hash_ns);
+        msSetError( MS_MISCERR, 
+                    "Unable to execute '%s' on pointer %p",
+                    "msXmpWrite()", "xmp_files_put_xmp", f );
+        return MS_FAILURE;
+    }
+
+    /* Write out the file and flush */
+    if ( !xmp_files_close(f, XMP_CLOSE_SAFEUPDATE) )
+    {
+        msFreeHashItems(&hash_ns);
+        msSetError( MS_MISCERR, 
+                    "Unable to execute '%s' on pointer %p",
+                    "msXmpWrite()", "xmp_files_close", f );
+        return MS_FAILURE;
+    }
+
+    msFreeHashItems(&hash_ns);
+    xmp_free(xmp);
+    xmp_terminate();
+
+    return MS_SUCCESS;
+
+#else
+    return MS_FAILURE;
+#endif
+}
+



More information about the mapserver-commits mailing list