2009-10-01 7 views
3

Je suis nouveau sur Postgres et SQL. J'ai créé le script suivant qui trace une ligne d'un point à un point projeté sur la ligne la plus proche. Cela fonctionne bien sur un petit ensemble de données de 5 à 10 points avec le même nombre de lignes; Cependant, sur 60 points avec 2000 lignes, la requête prend environ 12 heures. il est basé sur une fonction plus proche voisin collé ci-dessous et de http://www.bostongis.com/downloads/pgis_nn.txtSlow Postgres Query

EDIT documentation sur pgis_fn_nn est disponible sur http://www.bostongis.com/PrinterFriendly.aspx?content_name=postgis_nearest_neighbor_generic

La partie lente est la mise en œuvre de pgis_fn_nn (...)

  1. Qu'est-ce que je fais mal?
  2. Y a-t-il des conseils pour accélérer les choses?
  3. Y a-t-il un moyen d'améliorer les deux scripts?
  4. Que recommanderiez-vous si je veux combiner les deux requêtes en une?

my_script.sql

-- this sql script creates a line table that connects points from a point table 
-- to the projected points from the nearest line to the point of oritin 

-- delete duplicate tables if they exist 
DROP TABLE exploded_roads; 
DROP TABLE projected_points; 
DROP TABLE lines_from_centroids_to_roads; 

-- create temporary exploaded lines table 
CREATE TABLE exploded_roads (
the_geom geometry, 
edge_id serial 
); 

-- insert the linestring that are not multistring 
INSERT INTO exploded_roads 
SELECT the_geom 
FROM "StreetCenterLines" 
WHERE st_geometrytype(the_geom) = 'ST_LineString'; 

-- insert the linestrings that need to be converted from multi string 
INSERT INTO exploded_roads 
SELECT the_geom 
FROM (
    SELECT ST_GeometryN(
the_geom, 
generate_series(1, ST_NumGeometries(the_geom))) 
    AS the_geom 
    FROM "StreetCenterLines" 
) 
AS foo; 

-- create projected points table with ids matching centroid table 
CREATE TABLE projected_points (
the_geom geometry, 
pid serial, 
dauid int 
); 

-- Populate Table 
-- code based on Paul Ramsey's site and Boston GIS' NN code 
INSERT INTO projected_points(the_geom, dauid) 
SELECT DISTINCT ON ("DAUID"::int) 
( 
    ST_Line_Interpolate_Point(
    (
    SELECT the_geom 
    FROM exploded_roads 
    WHERE edge_id IN 
    (
     SELECT nn_gid 
     FROM pgis_fn_nn(centroids.the_geom, 30000000, 1,10, 'exploded_roads', 'true', 'edge_id', 'the_geom') 
    ) 
    ), 
    ST_Line_Locate_Point(
    exploded_roads.the_geom, 
    centroids.the_geom 
    ) 
) 
), 
(centroids."DAUID"::int) 

FROM exploded_roads, fred_city_o6_da_centroids centroids; 


-- Create Line tables 
CREATE TABLE lines_from_centroids_to_roads (
the_geom geometry, 
edge_id SERIAL 
); 


-- Populate Line Table 
INSERT INTO lines_from_centroids_to_roads(
SELECT 
ST_MakeLine(centroids.the_geom, projected_points.the_geom) 
FROM projected_points, fred_city_o6_da_centroids centroids 
WHERE projected_points.dauid = centroids.id 
); 

pgis_fn_nn de http://www.bostongis.com/downloads/pgis_nn.txt

---LAST UPDATED 8/2/2007 -- 
CREATE OR REPLACE FUNCTION expandoverlap_metric(a geometry, b geometry, maxe double precision, maxslice double precision) 
    RETURNS integer AS 
$BODY$ 
BEGIN 
    FOR i IN 0..maxslice LOOP 
     IF expand(a,maxe*i/maxslice) && b THEN 
      RETURN i; 
     END IF; 
    END LOOP; 
    RETURN 99999999; 
END; 
$BODY$ 
LANGUAGE 'plpgsql' IMMUTABLE; 

CREATE TYPE pgis_nn AS 
    (nn_gid integer, nn_dist numeric(16,5)); 

CREATE OR REPLACE FUNCTION _pgis_fn_nn(geom1 geometry, distguess double precision, numnn integer, maxslices integer, lookupset varchar(150), swhere varchar(5000), sgid2field varchar(100), sgeom2field varchar(100)) 
    RETURNS SETOF pgis_nn AS 
$BODY$ 
DECLARE 
    strsql text; 
    rec pgis_nn; 
    ncollected integer; 
    it integer; 
--NOTE: it: the iteration we are currently at 
--start at the bounding box of the object (expand 0) and move up until it has collected more objects than we need or it = maxslices whichever event happens first 
BEGIN 
    ncollected := 0; it := 0; 
    WHILE ncollected < numnn AND it <= maxslices LOOP 
     strsql := 'SELECT currentit.' || sgid2field || ', distance(ref.geom, currentit.' || sgeom2field || ') as dist FROM ' || lookupset || ' as currentit, (SELECT geometry(''' || CAST(geom1 As text) || ''') As geom) As ref WHERE ' || swhere || ' AND distance(ref.geom, currentit.' || sgeom2field || ') <= ' || CAST(distguess As varchar(200)) || ' AND expand(ref.geom, ' || CAST(distguess*it/maxslices As varchar(100)) || ') && currentit.' || sgeom2field || ' AND expandoverlap_metric(ref.geom, currentit.' || sgeom2field || ', ' || CAST(distguess As varchar(200)) || ', ' || CAST(maxslices As varchar(200)) || ') = ' || CAST(it As varchar(100)) || ' ORDER BY distance(ref.geom, currentit.' || sgeom2field || ') LIMIT ' || 
     CAST((numnn - ncollected) As varchar(200)); 
     --RAISE NOTICE 'sql: %', strsql; 
     FOR rec in EXECUTE (strsql) LOOP 
      IF ncollected < numnn THEN 
       ncollected := ncollected + 1; 
       RETURN NEXT rec; 
      ELSE 
       EXIT; 
      END IF; 
     END LOOP; 
     it := it + 1; 
    END LOOP; 
END 
$BODY$ 
LANGUAGE 'plpgsql' STABLE; 

CREATE OR REPLACE FUNCTION pgis_fn_nn(geom1 geometry, distguess double precision, numnn integer, maxslices integer, lookupset varchar(150), swhere varchar(5000), sgid2field varchar(100), sgeom2field varchar(100)) 
    RETURNS SETOF pgis_nn AS 
$BODY$ 
    SELECT * FROM _pgis_fn_nn($1,$2, $3, $4, $5, $6, $7, $8); 
$BODY$ 
    LANGUAGE 'sql' STABLE; 
+0

Quelle est la raison d'utiliser des procédures stockées sur le client code secondaire? –

Répondre

4

J'utilise la fonction "le plus proche" de faire pgRouting sur les données OpenStreetMap. Au début, je suis tombé sur la fonction fn_nn que vous mentionnez aussi, mais une visite à la chaîne #postgis irc sur irc.freenode.net m'a aidé. Il s'avère que postgis a des fonctions linéaires fantastiques qui, une fois combinées, répondent à tout ce dont vous avez besoin!

Vous pouvez trouver plus sur les fonctions linéaires à: http://postgis.refractions.net/documentation/manual-1.3/ch06.html#id2578698 mais voici comment j'implémenté

select 
    line_interpolate_point(ways.the_geom, 
    line_locate_point(ways.the_geom, pnt))),')','')) as anchor_point, 
    -- returns the anchor point 
    line_locate_point(ways.the_geom, pnt) as anchor_percentage, 
    -- returns the percentage on the line where the anchor will 
    -- touch (number between 0 and 1) 
    CASE 
    WHEN line_locate_point(ways.the_geom, pnt) < 0.5 THEN ways.source 
    WHEN line_locate_point(ways.the_geom, pnt) > 0.5 THEN ways.target 
    END as node, 
    -- returns the nearest end node id 
    length_spheroid(st_line_substring(ways.the_geom,0, 
    line_locate_point(ways.the_geom, pnt)), 
    'SPHEROID[\"WGS 84\",6378137,298.257223563]') as length, 
    distance_spheroid(pnt, line_interpolate_point(ways.the_geom, 
    line_locate_point(ways.the_geom, pnt)), 
    'SPHEROID[\"WGS 84\",6378137,298.257223563]') as dist 
      from ways, planet_osm_line, 
      ST_GeomFromText('POINT(1.245 51.234)', 4326) as pnt 
    where ways.gid = planet_osm_line.osm_id 
      order by dist asc limit 1;"; 

Espérons que cela est d'aucune utilité pour vous

+0

merci pour la réponse; cependant, il semble que le code renvoie le point le plus proche sur une ligne, et si vous avez 2 000 lignes, comment renvoie-t-il la ligne la plus proche du point? – dassouki

+1

la fonction line_locate_point (table.column, real_point) effectuera une recherche dans tout l'ensemble de données, dans mon cas 58000 éléments de ligne. la clause "ordre par dist asc limite 1" me donnera un résultat avec un seul élément de ligne où la distance est la plus courte. Trouver? – milovanderlinden