Top 5 Recent Articles
ARTICLES CATEGORIES
- Algorithms (22)
- All (399)
- Biography (1)
- Blog (44)
- Business Requirements (1)
- Commentary (1)
- Conversion (2)
- Customers (2)
- Data Models (1)
- Education (2)
- GeoRaptor (13)
- GPS (1)
- Image Processing (2)
- Import Export (8)
- Licensing (2)
- LiDAR (1)
- Linear Referencing (4)
- Manifold GIS (3)
- Mapping (1)
- MySQL Spatial (7)
- Networking and Routing (including Optimization) (5)
- Open Source (18)
- Oracle Spatial and Locator (194)
- Partitioning (1)
- PostGIS (36)
- Projections (1)
- Published Articles (1)
- qGIS (1)
- Recommendations (1)
- Services (1)
- Software Change Log (1)
- Source Code (37)
- Space Curves (9)
- Spatial Database Functions (109)
- Spatial DB comparison (1)
- Spatial XML Processing (11)
- SQL Server Spatial (92)
- Standards (3)
- Stored Procedure (17)
- Tessellation or Gridding (10)
- Tools (2)
- Topological Relationships (1)
- Training (2)
- Triangulation (2)
Calling the Oracle Spatial shapefile loader from within the Oracle database itself
Oracle makes available for download from its website a shapefile loader called shp2sdo. Loading data using this tool is a four step process:
- Execute shp2sdo to create a set of sqlplus and sqlldr files.
- Use SQLPlus to run the SQL file generated by shp2sdo to create the table and user_sdo_geom_metadata entries.
- Load the actual data (stored in files generated by shp2sdo) into the created table using sqlldr.
- Finally, run SDO_GEOM.VALIDATE_GEOMETRY to identify incorrect geometries, correct them using SDO_UTIL.RECTIFY_GEOMETRY and other tools, processing and, finally, create spatial indexes etc.
The shp2sdo tool is an operating system tool and so runs outside of the Oracle database processes. To effect a load of a shapefile from within the database one needs a method of being able to execute the process. There are a number of ways to do this but this blog article will concentrate on one: the use of the Java Virtual Machine that ships with every Oracle database.
Firstly, we need to write a Java class that will allow us to execute an external program. There are many ways to do this as any Googling of the relevant terms shows. I did this and chose a simple approach that does not handle stdin, stdout or stderr (and so is limited as to the error notification that can be generated).
Here is my class.
package com.spatialdbadvisor.gis.oracle; import java.io.IOException; import java.sql.DriverManager; import java.sql.SQLException; public class utilities { public static int RunCommand(String command) { int exitVal = 0; try { Runtime rt = Runtime.getRuntime(); Process proc = rt.exec(command); proc.waitFor(); exitVal = proc.exitValue(); } catch (Exception e) { System.out.println(e.getMessage()); exitVal = -1; } // By convention, 0 indicates normal termination. return exitVal; } }
Note that the function expects a single command string so if you want to pass parameters to an executable you have to put them all together with their command. See later for example.
We compile this using the java compiler that is shipped with the Oracle database (as the versions have to be exactly the same).
Since I did this work on Windows I created a cmd file which contained the following:
@ECHO OFF SET ORACLE_DB_HOME=C:\oracle\product\10.2.0\db_1 SET CLASSPATH=%ORACLE_DB_HOME%\jdk\jre\lib SET JAVA_HOME=%ORACLE_DB_HOME%\jdk SET PATH=%JAVA_HOME%\bin;%ORACLE_DB_HOME%\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem %JAVA_HOME%\bin\javac -classpath .;%ORACLE_DB_HOME%\lib\xmlparserv2.jar;%ORACLE_DB_HOME%\jdbc\lib\ojdbc14.jar;%ORACLE_DB_HOME%\md\lib\sdoutl.jar;%ORACLE_DB_HOME%\md\lib\sdoapi.jar -d C:\Temp\DBUtilities\classes C:\temp\DBUtilities\src\com\spatialdbadvisor\gis\oracle\utilities.java
Once this is compiled, we then use the Oracle loadjava tool to load the java class we have just compiled into Oracle. Again, I wrapped this inside a WIndows cmd tool.
ECHO OFF SET ORACLE_DB_HOME=C:\oracle\product\10.2.0\db_1 SET CLASSPATH=%ORACLE_DB_HOME%\jdk\jre\lib SET JAVA_HOME=%ORACLE_DB_HOME%\jdk SET PATH=%JAVA_HOME%\bin;%ORACLE_DB_HOME%\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem IF %ORACLE_SID%_ EQU _ SET ORACLE_SID=GISDB SET /P ousr=Enter codesys username (codesys): IF %ousr%_ EQU _ SET ousr=CODESYS SET /P opwd=Enter %ouser% password (codemgr): IF %opwd%_ EQU _ SET opwd=CODEMGR SET /P osid=Enter TNSName (%ORACLE_SID%): IF %osid%_ EQU _ SET osid=%ORACLE_SID% loadjava -user %ousr%/%opwd%%osid% -r -v -grant public -f C:\Temp\DBUtilities\classes\com\spatialdbadvisor\gis\oracle\utilities.class
Which, if successful, should give messages like this:
E:\Temp\DBUtilities\PLSQL>load_java_class Enter codesys username (codesys): Enter password (codemgr): Enter TNSName (GISDB): arguments: '-user' 'CODESYS/CODEMGR@GISDB' '-r' '-v' '-grant' 'public' '-f' 'C:\ temp\DBUtilities\classes\com\spatialdbadvisor\gis\oracle\utilities.cl ass' creating : class com/spatialdbadvisor/gis/oracle/utilities loading : class com/spatialdbadvisor/gis/oracle/utilities granting : execute on class com/spatialdbadvisor/gis/oracle/utilities to public resolving: class com/spatialdbadvisor/gis/oracle/utilities Classes Loaded: 1 Resources Loaded: 0 Sources Loaded: 0 Published Interfaces: 0 Classes generated: 0 Classes skipped: 0 Synonyms Created: 0 Errors: 0
So far, so good.
We now have a compiled Java class in the Oracle JVM,
But how do we execute it?
Firstly we have to create a PL/SQL function wrapper over the top of it as follows.
create or replace Function RunCommand( p_command in varchar2 ) Return Number As language java name 'com.spatialdbadvisor.gis.oracle.utilities.RunCommand(java.lang.String) return oracle.sql.string';
Now, we have the ability to execute an external program.
Or do we?
The Oracle Database is a very secure application. No organisation or the DBA it entrusts to ensure data security, wants to jeopordise database secutiry. Oracle’s JVM’s security is managed from the database. We can’t just have our new tool execute any program reading and writing data anywhere on disk. It has to be controlled.
Oracle does this via DBMS_JAVA’s GRANT_PERMISSION procedure. This procedure allows the DBA to grant read, write, execute, delete permissions directly to a user or via a role.
So, we need to use this to grant appropriate permissions.
For this article I copied the Oracle shp2sdo.exe application and the GeoScience Australia GeoData250K shapefiles (GeoData250K_Tas_roads.*) to the C:\Temp directory. I will do all my processing in this directory.
So, first, we need to grant appropriate permissions. As the “system” user I executed:
execute dbms_java.grant_permission( 'CODESYS', 'SYS:java.io.FilePermission', 'c:\Temp\*', 'read,write,execute,delete' );
Now we are ready to load our data.
Remember that our load is a multi-step process in which three of the processes have to be executed externally and one internally. So we now have some choices.
- Do we execute all the steps individually or
- Do we wrap all the external processes up into a single process and execute it followed by the post-load internal processes?
Because there are a number of issues relating to flow of control, operating system environment settings etc, I decided to use step two and put all three processing steps into a single command shell (or Linux/Unix shell).
Now, Windows shell programming is inflexible (Linux shell programming is far better), so please do not get too critical of what follows! To control the external processes I created a Windows command tool called shp2sdo.cmd. Here is what it does.
echo off REM Script Usage: <user> <password> <sid> <shapefile> <tablename> <geometry column> <id column> <start_id> <xmin> <xmax> <ymin> <ymax> <srid> rem shp2sdo.cmd GeoData250K_Tas_roads GeoData250K_Tas_roads geom gid 1 -180 180 -90 90 0.05 8311 REM SET _directory=%1 IF NOT EXIST %_directory% GOTO NOWINDIR SHIFT SET _ousr=%1 SHIFT SET _opwd=%1 SHIFT SET _osid=%1 SHIFT SET _shapefile=%1 IF NOT EXIST %_directory%\%_shapefile%.shp GOTO NOSHAPEFILE SET _tablename=%2 SET _geomcolumn=%3 SET _idcolumn=%4 SET _start_id=%5 SET _xmin=%6 SET _xmax=%7 SET _ymin=%8 SET _ymax=%9 shift SET _tolerance=%9% shift SET _srid=%9% REM Note that for some strange reason Oracle will execute this batch file from: %ORACLE_HOME%\DATABASE REM eg c:\oracle\product\10.2.0\db_1\DATABASE so we must ensure that the data And the Shp2Sdo REM executable all exist within the directory we have read/write/execute permissions from within Oracle. REM This is NOT cross-platform as it is only an example for publication cd %_directory% SET > %_directory%\%shapefile%shapefile%log echo shp2sdo -o %_shapefile% %_tablename% -g %_geomcolumn% -i %_idcolumn% -n %_start_id% -d -x (%_xmin%,%_xmax%) -y (%_ymin%,%_ymax%) -t %_tolerance% -s %_srid% >> %_directory%\%shapefile%.log REM USAGE: shp2sdo [-o] <shapefile> <tablename> -g <geometry column> REM -i <id column> -n <start_id> -p -d REM -x (xmin,xmax) -y (ymin,ymax) -s <srid> REM %_directory%\shp2sdo -o %_directory%\%_Shapefile% %_tablename% -g %_geomcolumn% -i %_idcolumn% -n %_start_id% -d -x (%_xmin%,%_xmax%) -y (%_ymin%,%_ymax%) -t %_tolerance% -s %_srid% REM Create the table echo sqlplus %_ousr%/%_opwd%%_osid% %_tablename%.sql >> %shapefile%.log Rem The Oracle tool does not put an EXIT or QUIT at the end of %_tablename%.sql so add it. echo EXIT; >> %_tablename%.sql sqlplus %_ousr%/%_opwd%%_osid% %_directory%\%_tablename%.sql echo sqlldr %_ousr%/%_opwd%%_osid% %_directory%\%_tablename% >> %shapefile%.log REM Finally, load the data using sql*loader. sqlldr %_ousr%/%_opwd%@%_osid% %_directory%\%_tablename% GOTO EXITSCRIPT :NOSHAPEFILE ECHO %_directory%\%_shapefile%.shp does not exist >> %_directory%\%shapefile%.log GOTO EXITSCRIPT :NOWINDIR ECHO %_directory% does not exist >> %_directory%\%shapefile%.log :EXITSCRIPT exit REM ===================================================================================
Which we would execute this way:
declare v_err integer; begin v_err := RunCommand('c:\temp\shp2sdo.cmd c:\temp gis gis GISDB GeoData250K_Tas_roads GeoData250K_Tas_roads geom gid 1 -180 180 -90 90 0.05 8311' ); dbms_output.put_line('RunCommand (0==SUCCESS) = ' || v_err); end; RunCommand (0==SUCCESS) = 0
After executing RunCommand, we can check the results in SQL Developer.
And then go on to create spatial indexes etc. (This could be added to the above processing: I leave that as an exercise for the interested used.)
This is pretty neat and simple processing (I do not present myself as a seasoned or experienced Java programmer). However, note that most DBAs get very worried about this sort of processing so, if you are thinking about implementing this sort of processing in your database, make sure you provide your DBA with lots of chocolates and beer.
I hope this article is useful to someone.
Documentation
- MySQL Spatial General Functions
- Oracle LRS Objects
- Oracle Spatial Exporter (Java + pl/SQL)
- Oracle Spatial Object Functions
- Oracle Spatial Object Functions (Multi Page)
- PostGIS pl/pgSQL Functions
- SC4O Oracle Java Topology Suite (Java + pl/SQL)
- SQL Server Spatial General TSQL Functions
- SQL Server Spatial LRS TSQL Functions