/* * File: EsriShapeExport.java * OptiMetrics, Inc. * 2107 Laurel Bush Road - Suite 209 * Bel Air, MD 21015 * (410)569 - 6081 */ package com.bbn.openmap.dataAccess.shape; import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.*; import javax.swing.*; import com.bbn.openmap.Environment; import com.bbn.openmap.MapBean; import com.bbn.openmap.omGraphics.*; import com.bbn.openmap.dataAccess.shape.output.*; import com.bbn.openmap.proj.Projection; import com.bbn.openmap.util.ColorFactory; import com.bbn.openmap.util.Debug; /** * Provides methods for saving OMGraphicLists as ShapeFiles. This * code was originally submitted by Karl Stuempfle of OptiMetrics, and * I modified it a little to add a user interface to modify the DBF * files if the user wants to.
* * Since Shape files can only hold one type of graphic, this class * will create one to three different lists as needed, for points, * lines and polygons.
* * If the OMGraphicList's AppObject holds a DbfTableModel, it will be * used for the shape file database file. */ public class EsriShapeExport implements ShapeConstants, OMGraphicConstants { /** * The source graphics to write to a shape file. */ protected OMGraphicList graphicList = null; /** * The optional DbfTableModel that describes properties for the * OMGraphics. This should be set in the AppObject of the * OMGraphicList. */ protected DbfTableModel masterDBF = null; /** * The projection needed to convert other OMGraphicTypes to * polygons. */ protected Projection projection; /** * The path where the shape files should be written. */ protected String filePath; /** * Gets set automatically if Debug.debugging("shape"); */ protected boolean DEBUG = false; /** * A list of ESEInterface classes, holding information for * different type ESRIGraphicLists created from the OMGraphicList. */ protected ArrayList eseInterfaces = new ArrayList(); /** * Flag for whether the DBF file should be written when the * OMGraphicList is exported to a .shp/.shx file. The .dbf file * will be created if set to true, and this is true by default. */ protected boolean writeDBF = true; /** * Flad to note whether, if a DbfTableModel is set, to add the * rendering information (DrawingAttributes contents) about the * OMGraphics to the contents of the DbfTableModel. False by * default. Doesn't do anything yet. */ protected boolean dbfHasRenderingInfo = false; /** * Create an EsriShapeExport object. * @param list the OMGraphicList to export. * @param proj the Projection of the map, needed to convert some * OMGraphic types to OMPolys. * @param pathToFile the file path of the shape file to save to. * If null, the user will be queried. If not null, the files will * be saved without any GUI confirmation. */ public EsriShapeExport(OMGraphicList list, Projection proj, String pathToFile) { setGraphicList(list); projection = proj; filePath = pathToFile; DEBUG = Debug.debugging("shape"); } /** * Set the OMGraphicList to use for export. If the AppObject in * the OMGraphicList holds a DbfTableModel, it will be used in the * export. */ public void setGraphicList(OMGraphicList list) { graphicList = list; if (list != null) { Object obj = list.getAppObject(); if (obj instanceof DbfTableModel) { masterDBF = (DbfTableModel) obj; Debug.message("shape", "Setting master DBF in ESE"); } } } public OMGraphicList getGraphicList() { return graphicList; } public void setProjection(Projection proj) { projection = proj; } public Projection getProjection() { return projection; } public void setFilePath(String pathToFile) { filePath = pathToFile; } public String getFilePath() { return filePath; } protected EsriPolygonList polyList = null; protected EsriPolylineList lineList = null; protected EsriPointList pointList = null; /** * Return the polygon list, create it if needed. */ protected EsriPolygonList getPolyList() { if (polyList == null) { polyList = new EsriPolygonList(); polyList.setTable(getMasterDBFHeaderClone()); } return polyList; } /** * Return the line list, create it if needed. */ protected EsriPolylineList getLineList() { if (lineList == null) { lineList = new EsriPolylineList(); lineList.setTable(getMasterDBFHeaderClone()); } return lineList; } /** * Return the point list, create it if needed. If the masterDBF * object exists, then a new one is created, which matching * structure, and put in the AppObject of the new list that is * returned. If there isn't a masterDBF object, then the * AppObject is set to null, and a default one will be created. */ protected EsriPointList getPointList() { if (pointList == null) { pointList = new EsriPointList(); pointList.setTable(getMasterDBFHeaderClone()); } return pointList; } /** * Add a graphic to the list, and add the record to the list's * DbfTableModel if both exist. */ protected void addGraphic(EsriGraphicList egl, OMGraphic graphic, ArrayList record) { egl.add(graphic); DbfTableModel dtm = egl.getTable(); if (dtm != null && record != null) { dtm.addRecord(record); } } /** Scoping method to call addGraphic with the right list. */ protected void addPolygon(OMGraphic graphic, ArrayList record) { addGraphic(getPolyList(), graphic, record); } /** Scoping method to call addGraphic with the right list. */ protected void addLine(OMGraphic graphic, ArrayList record) { addGraphic(getLineList(), graphic, record); } /** Scoping method to call addGraphic with the right list. */ protected void addPoint(OMGraphic graphic, ArrayList record) { addGraphic(getPointList(), graphic, record); } /** * Set the DbfTableModel representing the dbf file for the main * OMGraphicList. Can also be passed to this object as an * appObject within the top level OMGraphicList. */ public void setMasterDBF(DbfTableModel dbf) { masterDBF = dbf; } /** * Get the DbfTableModel representing the dbf file for the main * OMGraphicList. */ public DbfTableModel getMasterDBF() { return masterDBF; } /** * Set whether the DBF file should be written when the * OMGraphicList is exported to a .shp/.shx file. The .dbf file * will be created if set to true, and this is true by default. */ public void setWriteDBF(boolean value) { writeDBF = value; } /** * Get whether the DBF file should be written when the * OMGraphicList is exported to a .shp/.shx file. */ public boolean getWriteDBF() { return writeDBF; } /** * Get whether the DBF file should have the DrawingAttributes * information added to the DbfTableModel if it isn't already * there. */ public void setDBFHasRenderingInfo(boolean value) { dbfHasRenderingInfo = value; } /** * Get whether the DBF file should have the DrawingAttributes * information added to the DbfTableModel if it isn't already * there. */ public boolean getDBFHasRenderingInfo() { return dbfHasRenderingInfo; } /** * If the OMGraphicList has a DbfTableModel in its AppObject slot, * a new DbfTableModel is created that has the same structure. * @return DbfTableModel that matches the structure that is in the * OMGraphicList AppObject. */ protected DbfTableModel getMasterDBFHeaderClone() { if (masterDBF != null) { return masterDBF.headerClone(); } return null; } /** * Gets the DbfTableModel record at the index. Used when the * OMGraphicList contents are being split up into different type * EsriGraphicLists, and the records are being split into * different tables, too. */ protected ArrayList getMasterDBFRecord(int index) { try { if (masterDBF != null) { return (ArrayList)masterDBF.getRecord(index); } } catch (IndexOutOfBoundsException ioobe) { } return null; } /** * Separates the graphics from the OMGraphicList into Polygon, * Polyline and Point lists, then passes the desired shape lists * to their respective export functions to be added to an * EsriLayer of the same type and prepared for export.
* Separating the graphics into the three types is necessary due
* to shape file specification limitations which will only allow
* shape files to be of one type.
*/
public void export() {
OMGraphicList list = getGraphicList();
if (list == null) {
Debug.error("EsriShapeExport: no graphic list to export!");
return;
}
export(list, null, true);
}
/**
* A counter for graphics that are not RENDERTYPE_LATLON.
*/
int badGraphics;
/**
* This method is intended to allow embedded OMGraphicLists to be
* handled. The record should be set if the list is an embedded
* list, reusing a record from the top level interation. Set the
* record to null at the top level iteration to cause the method
* to fetch the record from the masterDBF, if it exists.
* @param list the OMGraphicList to write
* @param record the record for the current list, used if the list
* is actually a multipart geometry for the overall list. May be
* null anyway, though.
* @deprecated use export(OMGraphicList, ArrayList, boolean) instead.
* @see #export(OMGraphicList list, ArrayList record, boolean writeFiles)
*/
protected void export(OMGraphicList list, ArrayList record) {
export(list, record, true);
}
/**
* This method is intended to allow embedded OMGraphicLists to be
* handled. The record should be set if the list is an embedded
* list, reusing a record from the top level interation. Set the
* record to null at the top level iteration to cause the method
* to fetch the record from the masterDBF, if it exists.
* @param list the OMGraphicList to write
* @param record the record for the current list, used if the list
* is actually a multipart geometry for the overall list. May be
* null anyway, though.
* @param writeFiles Flag to note when this method is being called
* iteratively, which is when record is not null. If it is
* iterative, then the writing of the files should not be
* performed on this round of the method call.
*/
protected void export(OMGraphicList list, ArrayList record, boolean writeFiles) {
badGraphics = 0;
if (list == null) {
return;
}
int dbfIndex = 0;
boolean iterative = false;
//parse the graphic list and fill the individual lists with
//the appropriate shape types
Iterator it = list.iterator();
while (it.hasNext()) {
OMGraphic dtlGraphic = (OMGraphic) it.next();
if (record == null) {
record = getMasterDBFRecord(dbfIndex++);
}
// If we have an OMGraphicList, interate through that one
// as well. We're not handling multi-part geometries yet.
if (dtlGraphic instanceof OMGraphicList) {
if (DEBUG) Debug.output("ESE: handling OMGraphicList");
export((OMGraphicList)dtlGraphic, record, false);
continue;
}
//check to be sure the graphic is rendered in LAT/LON
if (dtlGraphic.getRenderType() != RENDERTYPE_LATLON) {
badGraphics++;
continue;
}
if (dtlGraphic instanceof OMPoly) {
OMPoly omPoly = (OMPoly)dtlGraphic;
//verify that this instance of OMPoly is a polygon
if (isPolygon(omPoly)) {
if (DEBUG) Debug.output("ESE: handling OMPoly polygon");
addPolygon(dtlGraphic, record);
record = null;
}
//if it is not it must be a polyline and therefore
//added to the line list
else {
if (DEBUG) Debug.output("ESE: handling OMPoly line");
addLine(dtlGraphic, record);
record = null;
}
}
//(end)if (dtlGraphic instanceof OMPoly)
//add all other fully enclosed graphics to the polyList
else if (dtlGraphic instanceof OMRect) {
if (DEBUG) Debug.output("ESE: handling OMRect");
addPolygon((OMGraphic)EsriPolygonList.convert((OMRect)dtlGraphic), record);
record = null;
} else if (dtlGraphic instanceof OMCircle) {
if (DEBUG) Debug.output("ESE: handling OMCircle");
addPolygon((OMGraphic)EsriPolygonList.convert((OMCircle)dtlGraphic, projection), record);
record = null;
} else if (dtlGraphic instanceof OMRangeRings) {
if (DEBUG) Debug.output("ESE: handling OMRangeRings");
export(EsriPolygonList.convert((OMRangeRings)dtlGraphic, projection), record, false);
}
//add lines to the lineList
else if (dtlGraphic instanceof OMLine) {
if (DEBUG) Debug.output("ESE: handling OMLine");
addLine((OMGraphic)EsriPolylineList.convert((OMLine)dtlGraphic), record);
record = null;
}
//add points to the pointList
else if (dtlGraphic instanceof OMPoint) {
if (DEBUG) Debug.output("ESE: handling OMPoint");
addPoint(dtlGraphic, record);
record = null;
}
}
//(end)for (int i = 0; i < dtlGraphicList.size(); i++)
if (badGraphics > 0) {
// Information popup provider, it's OK that this gets dropped.
DrawingToolRenderException.notifyUserOfNonLatLonGraphics(badGraphics);
}
if (!writeFiles) {
// Punch the stack back up so that the initial call will
// write the files.
return;
}
boolean needConfirmation = false;
//call the file chooser if no path is given
if (filePath == null) {
filePath = getFilePathFromUser();
needConfirmation = true;
}
if (DEBUG) Debug.output("ESE: writing files...");
boolean needTypeSuffix = false;
//(end)if (filePath == null) call the appropriate methods to
//set up the shape files of their respective types
if (polyList != null) {
eseInterfaces.add(new ESEInterface(polyList, filePath, null));
needTypeSuffix = true;
}
if (lineList != null) {
eseInterfaces.add(new ESEInterface(lineList, filePath, (needTypeSuffix?"Lines":null)));
needTypeSuffix = true;
}
if (pointList != null) {
eseInterfaces.add(new ESEInterface(pointList, filePath, (needTypeSuffix?"Pts":null)));
}
if (needConfirmation) {
showGUI();
} else {
writeFiles();
}
}
/**
* The the Iterator of ESEIterators.
*/
protected Iterator getInterfaces() {
return eseInterfaces.iterator();
}
/**
* Just write the files from the ESEInterfaces.
*/
protected void writeFiles() {
Iterator it = getInterfaces();
while (it.hasNext()) {
((ESEInterface)it.next()).write();
}
}
protected JFrame frame = null;
/**
* Show the GUI for saving the Shape files.
*/
public void showGUI() {
if (frame == null) {
frame = new JFrame("Saving Shape Files");
frame.getContentPane().add(getGUI(), BorderLayout.CENTER);
// frame.setSize(400, 300);
frame.pack();
}
frame.setVisible(true);
}
/**
* Hide the Frame holding the GUI.
*/
public void hideGUI() {
if (frame != null) {
frame.setVisible(false);
}
}
/**
* Create the GUI for managing the different ESEIterators.
*/
public Component getGUI() {
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
JPanel interfacePanel = new JPanel();
interfacePanel.setLayout(new GridLayout(0, 1));
Iterator interfaces = getInterfaces();
int count = 0;
while (interfaces.hasNext()) {
interfacePanel.add(((ESEInterface)interfaces.next()).getGUI());
count++;
}
panel.add(interfacePanel, BorderLayout.CENTER);
if (count > 1) {
JLabel notification = new JLabel(" " + count + " Shape file sets needed:");
panel.add(notification, BorderLayout.NORTH);
}
JButton saveButton = new JButton("Save");
saveButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
writeFiles();
hideGUI();
}
});
JButton cancelButton = new JButton("Cancel");
cancelButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
hideGUI();
}
});
JPanel controlPanel = new JPanel();
controlPanel.add(saveButton);
controlPanel.add(cancelButton);
panel.add(controlPanel, BorderLayout.SOUTH);
return panel;
}
/**
* Prepares and returns a 7 column DbfTableModel to accept input
* for columns of TYPE_CHARACTER.
The default model used
* holds most of the DrawingAttributes of the OMGraphics.
*
*
* @param list the EsriGraphicList to create a DbfTableModel from.
* @return The completed DbfTableModel.
*/
public DbfTableModel createDefaultModel(EsriGraphicList list) {
if (DEBUG) Debug.output("ESE: creating DbfTableModel");
DbfTableModel _model = new DbfTableModel(7);
//Setup table structure
//column 0
//The first parameter, 0, respresents the first column
_model.setLength(0, (byte)50);
_model.setColumnName(0, SHAPE_DBF_DESCRIPTION);
_model.setType(0, (byte)DbfTableModel.TYPE_CHARACTER);
_model.setDecimalCount(0, (byte)0);
//column 1
//The first parameter, 1, respresents the second column
_model.setLength(1, (byte)10);
_model.setColumnName(1, SHAPE_DBF_LINECOLOR);
_model.setType(1, (byte)DbfTableModel.TYPE_CHARACTER);
_model.setDecimalCount(1, (byte)0);
//column2
//The first parameter, 2, respresents the third column
_model.setLength(2, (byte)10);
_model.setColumnName(2, SHAPE_DBF_FILLCOLOR);
_model.setType(2, (byte)DbfTableModel.TYPE_CHARACTER);
_model.setDecimalCount(2, (byte)0);
//column3
//The first parameter, 3, respresents the fourth column
_model.setLength(3, (byte)10);
_model.setColumnName(3, SHAPE_DBF_SELECTCOLOR);
_model.setType(3, (byte)DbfTableModel.TYPE_CHARACTER);
_model.setDecimalCount(3, (byte)0);
//column4
//The first parameter, 4, respresents the fifth column
_model.setLength(4, (byte)4);
_model.setColumnName(4, SHAPE_DBF_LINEWIDTH);
_model.setType(4, (byte)DbfTableModel.TYPE_NUMERIC);
_model.setDecimalCount(4, (byte)0);
//column5
//The first parameter, 5, respresents the sixth column
_model.setLength(5, (byte)20);
_model.setColumnName(5, SHAPE_DBF_DASHPATTERN);
_model.setType(5, (byte)DbfTableModel.TYPE_CHARACTER);
_model.setDecimalCount(5, (byte)0);
//column6
//The first parameter, 6, respresents the seventh column
_model.setLength(6, (byte)10);
_model.setColumnName(6, SHAPE_DBF_DASHPHASE);
_model.setType(6, (byte)DbfTableModel.TYPE_NUMERIC);
_model.setDecimalCount(6, (byte)4);
// At a later time, more stroke parameters can be addded, like
// dash phase, end cap, line joins, and dash pattern.
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
OMGraphic omg = (OMGraphic)iterator.next();
ArrayList record = new ArrayList();
// Description
Object obj = omg.getAppObject();
if (obj instanceof String) {
record.add(obj);
} else {
record.add("");
}
record.add(ColorFactory.getHexColorString(omg.getLineColor()));
record.add(ColorFactory.getHexColorString(omg.getFillColor()));
record.add(ColorFactory.getHexColorString(omg.getSelectColor()));
BasicStroke bs = (BasicStroke)omg.getStroke();
record.add(new Double(bs.getLineWidth()));
String dp = BasicStrokeEditor.dashArrayToString(bs.getDashArray());
if (dp == BasicStrokeEditor.NONE) {
dp = "";
}
record.add(dp);
record.add(new Double(bs.getDashPhase()));
_model.addRecord(record);
if (DEBUG) Debug.output("ESE: adding record: " + record);
}
return _model;
}
/**
* Takes an OMPoly as the parameter and checks whether
* or not it is a polygon or polyline.
*
* This method incorporates the OMPoly.isPolygon()
* method which returns true if the fill color is not
* clear, but also checks the first set and last set of
* lat/lon points of the float[] defined by
* OMPoly.getLatLonArray(). Returns true for a polygon
* and false for a polyline.
*
* @param omPoly the OMPoly object to be verified
* @return The polygon value
*/
public static boolean isPolygon(OMPoly omPoly) {
boolean isPolygon = false;
//get the array of lat/lon points
float[] points = omPoly.getLatLonArray();
int i = points.length;
//compare the first and last set of points, equal points
//verifies a polygon.
if (points[0] == points[i - 2] &&
points[1] == points[i - 1]) {
isPolygon = true;
}
//check OMPoly's definition of a polygon
if (omPoly.isPolygon()) {
isPolygon = true;
}
return isPolygon;
}
/**
* Generic error handling, puts up an error window.
*/
protected void handleException(Exception e) {
//System.out.println(e);
StringBuffer sb = new StringBuffer("ShapeFile Export Error:");
sb.append("\nProblem with creating the shapefile set.");
sb.append("\n" + e.toString());
JOptionPane.showMessageDialog(null, sb.toString(), "ESRI Shape Export to File", JOptionPane.ERROR_MESSAGE);
e.printStackTrace();
}
/**
* Fetches a file path from the user, via a JFileChooser. Returns
* null if the user cancels.
*/
public String getFilePathFromUser() {
String ret = null;
try {
//setup the file chooser
File startingPoint = new File(Environment.get("lastchosendirectory", System.getProperty("user.home")));
JFileChooser chooser = new JFileChooser(startingPoint);
chooser.setDialogTitle("Select ShapeFile Set Name...");
int state = chooser.showSaveDialog(null);
//only bother trying to read the file if there is one
//for some reason, the APPROVE_OPTION said it was a
//boolean during compile and didn't work in this next
//statement
if ((state != JFileChooser.CANCEL_OPTION) &&
(state != JFileChooser.ERROR_OPTION)) {
ret = chooser.getSelectedFile().getCanonicalPath();
//store the selected file for later
Environment.set("lastchosendirectory", ret);
} else {
// no need to try to continue if the user doesn't
// actually pick a filename
if (DEBUG) Debug.output("ESE: No output file chosen.");
}
} catch (IOException ioe) {
handleException(ioe);
}
return ret;
}
/**
* A helper class to manage a specific instance of a
* EsriGraphicList, it's data model, etc. Provides a GUI to
* display and change the name of the file, and the DbfTableModel
* GUI, and also writes the files out.
*/
public class ESEInterface {
protected EsriGraphicList list;
protected DbfTableModel model;
protected String suffix;
protected String filePath;
File shpFile = null;
File shxFile = null;
File dbfFile = null;
protected JTextField filePathField;
public ESEInterface(EsriGraphicList eglist,
String filePathString,
String fileNameSuffix) {
list = eglist;
filePath = filePathString;
model = eglist.getTable();
if (model == null) {
model = createDefaultModel(list);
}
model.setWritable(true);
suffix = (fileNameSuffix==null?"":fileNameSuffix);
}
public Component getGUI() {
JPanel panel = new JPanel();
int type = list.getType();
String sectionTitle;
switch (type) {
case (SHAPE_TYPE_POINT):
sectionTitle = "Point Shape File";
break;
case (SHAPE_TYPE_POLYLINE):
sectionTitle = "Line Shape File";
break;
case (SHAPE_TYPE_POLYGON):
sectionTitle = "Polygon Shape File";
break;
default:
sectionTitle = "Shape File";
}
panel.setBorder(
BorderFactory.createTitledBorder(
BorderFactory.createEtchedBorder(), sectionTitle));
panel.setLayout(new GridLayout(0, 1));
JPanel pathPanel = new JPanel();
filePathField = new JTextField(20);
filePathField.setText(filePath + suffix);
JButton filePathChooserLauncher = new JButton("Change Path");
filePathChooserLauncher.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
setFilePath(getFilePathFromUser());
}
});
panel.add(filePathField);
JButton editDBFButton = new JButton("Edit the Attribute File...");
editDBFButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
model.showGUI(getFilePath() + " Attributes", DbfTableModel.DONE_MASK | DbfTableModel.MODIFY_COLUMN_MASK);
}
});
pathPanel.add(editDBFButton);
pathPanel.add(filePathChooserLauncher);
panel.add(pathPanel);
return panel;
}
protected void setFilePath(String path) {
filePath = path;
}
public void write() {
if (filePathField != null) {
filePath = filePathField.getText();
}
if (filePath == null) {
filePath = getFilePathFromUser();
if (filePath == null) {
return;
}
}
shpFile = new File(filePath + ".shp");
shxFile = new File(filePath + ".shx");
dbfFile = new File(filePath + ".dbf");
try {
//create an esriGraphicList and export it to the shapefile set
if (DEBUG) Debug.output("ESE writing: " +
list.size() +
" elements");
ShpOutputStream pos =
new ShpOutputStream(new FileOutputStream(shpFile));
int[][] indexData = pos.writeGeometry(list);
ShxOutputStream xos =
new ShxOutputStream(new FileOutputStream(shxFile));
xos.writeIndex(indexData, list.getType());
if (getWriteDBF()) {
DbfOutputStream dos =
new DbfOutputStream(new FileOutputStream(dbfFile));
dos.writeModel(model);
}
} catch (Exception e) {
handleException(e);
}
}
}
}