January 23, 2010

GeoPDF Layers

As soon as you start making GeoPDFs you'll want to mess around with the map layers and reorganize them. A good first step is to list out all the layers in the PDF. In PDF-parlance, these are called Optional Content Groups (OCG). An OCG is a dictionary with a name and a type of OCG. The name is what is displayed in the layers table of contents in the PDF reader.

TerraGo's implementation of GeoPDF gives each OCG an alternate name of the format map#layer# (for layers in the source MXD, if you're exporting from TerraGO's ESRI library and using an MXD) or map#group# (folders in the source MXD)or map#surrounds# (for things like scalebars). These names are the keys in the Page>Resources>Properties dictionary. The TerraGo OCG names are very important if you want to assign an OCG to another folder. The place where you would change the default assignment is the data stream at Page>Contents, which looks a bit like this:

/OC /map3group1 BDC
/OC /map3group37 BDC
/OC /map3group38 BDC
/OC /map3layer39 BDC
q
1 0 0 1 0 0 cm
/TGO976A Do
Q
EMC
EMC
EMC
EMC

This tells us that map 3 group 1, contains map 3 group 37, contains map 3 group 38, which contains map 3 layer 39. The contents of layer 39 are in the objects aliased with /TG09751. Just make sure that when you modify this stream, the number of BDC tags equals the number of closing EMC tags, and that the number of little 'q' equals the number of big 'Q'.

Using the PDFTron library for Java here's a script that prints out the OCG object number, the OCG name that displays in the layer table of contents, and the OCG TerraGO name.

 PDFNet.initialize();

// Relative path to the folder containing test files.
String pdfName = "myTerraGoGeoPDF.pdf";

try
{
PDFDoc doc= new PDFDoc(pdfName);
//aka the /Resources object in the Page object
Obj mapLayers = doc.getPage(1).getSDFObj().findObj("Resources");
Obj mapLayersProps = mapLayers.findObj("Properties");

//create a hash map to keep track of the reversed /Properties dictionary key value pairs.
//Only reverse the key and value, because I will need to be looking up by value, not by key.
HashMap hm = new HashMap();
//create an iterator to go through all the values in the /Properties dictionary
DictIterator itr =mapLayersProps.getDictIterator();
while (itr.hasNext()){
//get the key and the value of the iterator
Obj key = itr.key();
Obj val = itr.value();
hm.put(val.getObjNum(), key.getName());
itr.next();
}

//get the OCG (Layers) in the PDF
Obj ocgs = doc.getOCGs();
int sz = (int)ocgs.size();
for (int i=0; i < sz; i++) {
Group ocg = new Group(ocgs.getAt(i));
long objNum = ocg.getSDFObj().getObjNum();
System.out.println(objNum + "\t" +ocg.getName() + "\t"+hm.get(objNum) );
}
doc.close();
}
catch(Exception e)
{
e.printStackTrace();
}

PDFNet.terminate();