First off there are some awesome new features coming very soon to SpatialKey! I can’t let the cat completely out of the bag, but by reading this article you may get some idea of what is coming…
One of the requirements for SpatialKey that has come up is the necessity to execute a command line tool from inside of our Java server implementation. If you are at all familiar with SpatialKey (and if not, what are you waiting for??? get on over to www.spatialkey.com) you know that it is a geo-spatial application. As such, we need to store and retrieve geo-spatial data and to accomplish this goal we have settled on using PostGreSQL along with PostGIS extensions.
One of the tools that comes with PostGIS is called shp2pgsql (shape to PostGreSQL). This tool allows for taking a geo-spatial shape file and converting the contents into PostGreSQL compatable SQL for database insertion (hint… hint…). These shapes can then be used for filtering in PostGIS SQL calls.
Here is a simple example of what using this tool would look like if run from a terminal window:
./shp2pgsql roads.shp roads_table my_db > roads.sql
Notice the output redirection ‘> roads.sql’ this unix operator simply dumps the result into the file specified and works great from a terminal window. The generated sql could then be run against the database to import the shape data. Unfortunately, however, this is not the case when run from within Java.
So, how do you run a command line tool from Java? It is pretty easy actually. And the good folks at Apache Commons have made the process pretty robust. I recommend using their command line execution package (download here).
Using their code the above example might look something like this:
CommandLine cmdLine = CommandLine.parse("./shp2pgsql roads.shp roads_table my_db > roads.sql");
DefaultExecutor executor = new DefaultExecutor();
executor.execute(cmdLine);
Unfortunately this does not work. The JVM thinks this entire line is a part of the shp2pgsql command when in reality they are two separate commands that the operating system needs to interpret. If we leave off the “> roads.sql” then everything works as it should, but you have no captured SQL. So how do we make this work? We need to capture the output stream from the command line execution:
ByteArrayOutputStream bos = new ByteArrayOutputStream();
CommandLine cmdLine = CommandLine.parse("./shp2pgsql roads.shp roads_table my_db");
PumpStreamHandler psh = new PumpStreamHandler(bos, System.out);
DefaultExecutor executor = new DefaultExecutor();
executor.setStreamHandler(psh);
executor.execute(cmdLine);
bos.close();
So the code has changed a bit here. First of all we define a ByteArrayOutputStream that will capture the data from the command execution. This output stream is passed as an argument to a new instance of a PumpStreamHandler object (this object is a part of the Apache Commons Exec code base). With this stream handler we can now tell the executor that we do not want the output stream to go to its default but instead to the ByteArrayOutputStream we defined. Also notice that I removed the offending “> roads.sql” from the original command line.
Once I had access to the stream I simply ran a toString() on the contents and executed the SQL. Of course if I really wanted the contents placed into a file I could have used a FileOutputStream instead.
That’s all there is to it.