Top 5 Recent Articles
- Biography (1)
- Blog (41)
- Changes (1)
- Customers (1)
- Data Models (1)
- Education (2)
- General Software (21)
- Georaptor Blog (5)
- Image Catalog (2)
- Licensing (1)
- ManifoldGIS (3)
- MySQL Blog (4)
- MySQL Spatial (3)
- Networking and Routing (including Optimization) (3)
- Oracle Spatial (171)
- Philosophy (1)
- PostGIS (30)
- Press Releases (1)
- Source code (24)
- Space Curves (1)
- Spatial DB comparison (1)
- SQL (1)
- SQL Server Blog (58)
- SQL Server Spatial (General) (15)
- SQL Server Spatial (LRS) (37)
- Stored Procedure (2)
- Training (1)
- XML (5)
Simple Implementation Of Travelling Salesman Problem
UPDATE: This function is now a part of the Oracle Object implementation.
The Travelling Salesman is a well known problem in graph theory:
The Travelling Salesman Problem describes a salesman who must travel between N cities.
The order in which he does so is something he does not care about, as long as he visits each one during his trip, and finishes where he was at first.
Each city is connected to other close by cities, or nodes, by airplanes, or by road or railway.
Each of those links between the cities has one or more weights (or the cost) attached.
The cost describes how “difficult” it is to traverse this edge on the graph, and may be given, for example, by the cost of an airplane ticket or train ticket,
or perhaps by the length of the edge, or time required to complete the traversal.
The salesman wants to keep both the travel costs, as well as the distance he travels as low as possible.
While there are many solutions out there in Java and other languages, I thought I would create an implementation using PL/SQL and Oracle Spatial alone. I do not profess that this is a true, or full, implementation of the problem, but it is an example of what could be done even in PL/SQL.
My implementation has two parts:
1. A function that is compiled against specific tables and which uses the nearest neighbour SDO_NN operator.
2. A separate function that runs against collections of gometries independently of the source of the data.
This article describes the first implementation. This implementation requires:
- A table of points coded only in SDO_POINT_TYPE;
- Points can be 2D or 3D;
- Table has unique integer column (either primary key or unique constraint/index);
- Table is spatially indexed;
One can create a sample dataset that implements these requirements as follows.
DROP TABLE ProjPoint2D PURGE; CREATE TABLE ProjPoint2D ( id INTEGER, geom mdsys.sdo_geometry ); INSERT INTO ProjPoint2D (id, geom) WITH mGeom AS ( SELECT SDO_GEOMETRY(2005,NULL,NULL, SDO_ELEM_INFO_ARRAY(1,1,20), SDO_ORDINATE_ARRAY(358443.819,5407099.906, 358716.043,5407087.437, 358651.624,5407224.588, 358680.717,5407399.144, 358952.941,5407444.861, 359314.521,5407415.768, 359370.628,5407249.525, 359349.847,5407137.31, 359108.794,5407089.515, 358930.082,5407241.213, 358826.18,5407336.803, 358819.946,5407463.563, 359056.843,5407552.919, 359283.35,5407615.261, 359075.545,5407523.827, 358485.38,5407735.787, 358393.946,5407552.919, 358312.903,5407311.866, 358319.137,5407087.437, 358360.698,5406991.847)) AS mPoint FROM dual ) SELECT t.id,sdo_geometry(2001,a.mPoint.sdo_srid,sdo_point_type(t.x,t.y,t.z),NULL,NULL) AS point FROM mGeom a, TABLE(sdo_util.getVertices(a.mPoint)) t; COMMIT; SELECT 'Inserted '||COUNT(*)||' records into ProjPoint2D' FROM ProjPoint2D; DELETE FROM USER_SDO_GEOM_METADATA WHERE TABLE_NAME = 'PROJPOINT2D'; COMMIT; DECLARE v_shape mdsys.sdo_geometry; v_srid INTEGER; BEGIN SELECT SDO_AGGR_MBR(a.geom), a.geom.sdo_srid INTO v_shape, v_srid FROM ProjPoint2D a GROUP BY a.geom.sdo_srid ; INSERT INTO USER_SDO_GEOM_METADATA(TABLE_NAME,COLUMN_NAME,DIMINFO,SRID) VALUES ('PROJPOINT2D','GEOM',MDSYS.SDO_DIM_ARRAY( MDSYS.SDO_DIM_ELEMENT('X',V_SHAPE.SDO_ORDINATES(1),V_SHAPE.SDO_ORDINATES(2),0.005), MDSYS.SDO_DIM_ELEMENT('Y',V_SHAPE.SDO_ORDINATES(3),V_SHAPE.SDO_ORDINATES(4),0.005)), v_srid); END; / SHOW ERRORS COMMIT; CREATE INDEX ProjPoint2D_GEOM ON ProjPoint2D(geom) INDEXTYPE IS mdsys.spatial_index parameters('sdo_indx_dims=2, layer_gtype=point');
The function has the following interface:
CREATE OR REPLACE FUNCTION ST_TravellingSalesman( p_table_name IN varchar2, p_table_gid_name IN varchar2, p_geom_column_name IN varchar2, p_table_gid_value IN NUMBER, p_unit IN varchar2 DEFAULT NULL ) RETURN sdo_geometry Deterministic AS ... etc ... BEGIN -- Algorithm starts with point (Node) identified by p_table_gid_value of column in table p_table_name called p_table_gid_name. -- Add first ID to list of nodes that have already been processed. -- Iterate over all points in table ---- For current node (gid) find nearest point (node) in table to it which is not in already processed node list. ---- Add found node XY(Z) values to return linestring. ---- Add found node ID to list of nodes that have already been processed RETURN path_as_linestring; END ST_TravellingSalesman; / SHOW errors
An example of how to call the function with our sample data is as follows.
SELECT st_travellingsalesman(p_table_name=>'PROJPOINT2D', - p_table_gid_name=>'ID', - p_geom_column_name =>'GEOM', - p_table_gid_value=>1, - p_unit=>NULL) AS path- FROM dual; -- Now let's test it PATH ---- SDO_GEOMETRY(2002,NULL,NULL,MDSYS.SDO_ELEM_INFO_ARRAY(1,2,1),MDSYS.SDO_ORDINATE_ARRAY(358443.819,5407099.906, 358319.137,5407087.437, 358360.698,5406991.847, 358312.903,5407311.866, 358393.946,5407552.919, 358485.38,5407735.787, 358680.717,5407399.144, 358819.946,5407463.563, 358826.18,5407336.803, 358930.082,5407241.213, 358952.941,5407444.861, 359075.545,5407523.827, 359056.843,5407552.919, 359283.35,5407615.261, 359314.521,5407415.768, 359370.628,5407249.525, 359349.847,5407137.31, 359108.794,5407089.515, 358716.043,5407087.437, 358651.624,5407224.588))
This looks like the following:
There is also a 2.5D linestring example which shows a different solution for the same XY points as it takes in to account the slope length between each of the points.
Contact me for the actual source code.
I hope this article helps someone out there.
Contact me for the actual source code.