I need some help in combining a stored procedure with another function.
I've got a stored procedure that pulls latitudes and longitudes from a database. I've got another function that checks whether a point is inside a polygon. My goal is to combine the two functions, so that I can check whether the latitude and longitude points pulled from the db are inside a specific area.
This stored procedure pulls latitude and longitudes from the database based on offense:
DROP PROCEDURE IF EXISTS latlongGrabber;
DELIMITER $$
CREATE PROCEDURE latlongGrabber(IN offense_in VARCHAR(255))
BEGIN
DECLARE latitude_val VARCHAR(255);
DECLARE longitude_val VARCHAR(255);
DECLARE no_more_rows BOOLEAN;
DECLARE latlongGrabber_cur CURSOR FOR
SELECT latitude, longitude FROM myTable WHERE offense = offense_in;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_more_rows = TRUE;
OPEN latlongGrabber_cur;
the_loop: LOOP
FETCH latlongGrabber_cur
INTO latitude_val, longitude_val;
IF no_more_rows THEN CLOSE latlongGrabber_cur;
LEAVE the_loop;
END IF;
SELECT latitude_val, longitude_val;
END LOOP the_loop;
END
$$
DELIMITER ;
This function checks whether a point is inside a polygon. I'd like the function to test the points produced by the procedure. I can hard-code the polygon for now. (Once, I know how to combine these two functions, I'll use the same pattern to pull the polygons from the database).
DROP FUNCTION IF EXISTS myWithin;
DELIMITER $$
CREATE FUNCTION myWithin(p POINT, poly POLYGON) RETURNS INT(1) DETERMINISTIC
BEGIN
DECLARE n INT DEFAULT 0;
DECLARE pX DECIMAL(9,6);
DECLARE pY DECIMAL(9,6);
DECLARE ls LINESTRING;
DECLARE poly1 POINT;
DECLARE poly1X DECIMAL(9,6);
DECLARE poly1Y DECIMAL(9,6);
DECLARE poly2 POINT;
DECLARE poly2X DECIMAL(9,6);
DECLARE poly2Y DECIMAL(9,6);
DECLARE i INT DEFAULT 0;
DECLARE result INT(1) DEFAULT 0;
SET pX = X(p);
SET pY = Y(p);
SET ls = ExteriorRing(poly);
SET poly2 = EndPoint(ls);
SET poly2X = X(poly2);
SET poly2Y = Y(poly2);
SET n = NumPoints(ls);
WHILE i<n DO
SET poly1 = PointN(ls, (i+1));
SET poly1X = X(poly1);
SET poly1Y = Y(poly1);
IF ( ( ( ( poly1X <= pX )
&& ( pX < poly2X ) ) || ( ( poly2X <= pX )
&& ( pX < poly1X ) ) )
&& ( pY > ( poly2Y - poly1Y ) * ( pX - poly1X ) / ( poly2X - poly1X ) + poly1Y ) ) THEN
SET result = !result;
END IF;
SET poly2X = poly1X;
SET poly2Y = poly1Y;
SET i = i + 1;
END WHILE;
RETURN result;
End
$$
DELIMITER ;
This function is called as follows:
SET #point = PointFromText('POINT(5 5)') ;
SET #polygon = PolyFromText('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))');
SELECT myWithin(#point, #polygon) AS result
I've tested the stored procedure and the function and they work well. I just have to figure out how to combine them. I'd like to call the procedure with the offense parameter and have it test all of the latitudes and longitudes pulled from the database to see whether they are inside or outside of the polygon.
Any advice or suggestions?
Thank you.
-Laxmidi
Chad at mysql's forum helped me solve this:
SELECT myWithin(
POINTFROMTEXT( CONCAT( 'POINT(', latitude, ' ', longitude, ')' ) ) , POLYFROMTEXT( 'POLYGON(( 0 0, 0 10, 10 10, 10 0, 0 0))' )
)AS result, latitude, longitude
FROM myTable
I can simply use a select statement calling the function. I didn't need the stored procedure after all.
Thanks.
-Laxmidi
Related
I have a procedure in mysql and it has 4 parameters as Input and 3 parameters of OUTPUT and one param of OUTPUT doesn't return nothing (null).
DELIMITER $$
drop procedure if exists `cierreMes`$$
create procedure cierreMes (in tarjeta varchar(100),in bancoBus varchar(100),in mes int,in anyo int, out total int, out nulas int, out erroneas int)
begin
declare stockActual int default 0;
declare cantidad int;
/*declare xcantidad,xnulas,xerroneas int;*/
declare entrada, salida int default 0;
declare total int default 0;
select stock
into stockActual
from almacen
where idProducto =
(select idProducto from productos where productos.banco = bancoBus and productos.plastico = tarjeta);
call entradasSalidas(tarjeta,bancoBus,mes,anyo,#ent,#sal);
set entrada = #ent;
set salida = #sal;
call obtenerMovimientosMes(tarjeta,bancoBus,mes,anyo,#cant,#nul,#err);
set cantidad = #cant;
set nulas = #nul;
set erroneas = #err;
set total =(stockActual + entrada) - (salida + cantidad);
select total;
end$$
DELIMITER ;
call cierreMes('4B MC','SANTANDER',3,2013, #total, #nulas, #erroneas);
select #total, #nulas, #erroneas;
When i do "call" #nulas and #erroneas return a value, but #total
nothing.
With select total, its works fine. but no returns a value, in this select : select #total, #nulas, #erroneas; #total is null.
You calculate total with this formula:
set total =(stockActual + entrada) - (salida + cantidad);
If one of the values used in the calculation is NULL, then total will be NULL.
I can see set statements for entrada, salida, and cantidad. But what is the value of stockActual?
It seems you are missing a statement that will set the value of stockActual.
I have the following stored procedure that is meant to implement Dijkstra's shortest path algorithm:
CREATE PROCEDURE `Dijkstras`(IN `pids` VARCHAR(512), IN `startP` VARCHAR(8), IN `endP` VARCHAR(8), OUT `dist` DECIMAL(20,10), OUT `eset` VARCHAR(1024))
BEGIN
DECLARE currentP VARCHAR(4);
DECLARE finished INT DEFAULT 0;
DECLARE pt_from, pt_to int;
DECLARE pt_dist decimal(20,10);
DECLARE done INT DEFAULT 0;
DECLARE cur2 CURSOR FOR
select F.id as `from`, T.id as `to`, dist(F.lat, F.lng, T.lat, T.lng)
as dist
from sampledata F, sampledata T
where F.id < T.id and
find_in_set(convert(F.id, char(10)), pids) and
find_in_set(convert(T.id, char(10)), pids)
order by dist;
DECLARE CONTINUE HANDLER FOR not found SET done = 1;
SET currentP= startP;
SET eset = '';
SET dist = 0;
SET done=0;
OPEN cur2; -- this finds pariwise distances in miles.
REPEAT
FETCH cur2 INTO pt_from, pt_to, pt_dist;
SET dist= dist+pt_dist;
SET eset= CONCAT(eset, ',');
IF(currentP=pt_from OR currentP=pt_to) AND
(IN_SET(pt_from,pids) AND IN_SET(pt_to,pids)) THEN
BEGIN
SET dist= dist+ pt_dist;
SET pids= REMOVE_MEMBER(currentP, pids);
SET eset = concat(eset, ',', concat(pt_from, ':', pt_to));
IF left(eset, 1) = ',' then
SET eset = substring(eset, 2); -- remove extra comma.
END IF;
IF currentP=pt_from THEN
SET currentP=pt_to;
ELSE
SET currentP=pt_from;
END IF;
IF currentP= endP THEN
SET finished= 1;
END IF;
END;
END IF;
UNTIL done
END REPEAT;
CLOSE cur2;
END
My issue is that the cursor isn't working properly. When I fetch the current row into pt_from, pt_to, and pt_dist all I get are NULL values. The sampledata table is properly stored in the database and all the point ids in pids are also in the sampledata table. Plus this EXACT code works for another procedure, but reusing it here isn't working.
Anybody know what I'm doing wrong?
The error was that I passed in the point ids like this '12, 15, 18' with spaces in between. MySQL counts the whitespace when it parses the strings, and the id's in the table were listed without spaces. The correct way to pass in the string set is '12,15,18'.
I was wondering, if there is some way to shuffle the letters of a string in mysql/sql, i.e. something like the pseudocode: SELECT SHUFFLE('abcdef')?
Couldn't find any from http://dev.mysql.com/doc/refman/5.0/en/string-functions.html and searching for it just seems to find solutions for shuffling results, not a string.
Here you go:
DELIMITER //
DROP FUNCTION IF EXISTS shuffle //
CREATE FUNCTION shuffle(
v_chars TEXT
)
RETURNS TEXT
NOT DETERMINISTIC -- multiple RAND()'s
NO SQL
SQL SECURITY INVOKER
COMMENT ''
BEGIN
DECLARE v_retval TEXT DEFAULT '';
DECLARE u_pos INT UNSIGNED;
DECLARE u INT UNSIGNED;
SET u = LENGTH(v_chars);
WHILE u > 0
DO
SET u_pos = 1 + FLOOR(RAND() * u);
SET v_retval = CONCAT(v_retval, MID(v_chars, u_pos, 1));
SET v_chars = CONCAT(LEFT(v_chars, u_pos - 1), MID(v_chars, u_pos + 1, u));
SET u = u - 1;
END WHILE;
RETURN v_retval;
END;
//
DELIMITER ;
SELECT shuffle('abcdef');
See sqlfiddle.com for the output.
Tested successfully with mariadb 10.1 (mysql 5.6 equivalent)
Edit: this solution is for Microsoft SQL Server.
As it's not allowed to use RAND() in user defined function, we create a view to use it later in our shuffle function:
CREATE VIEW randomView
AS
SELECT RAND() randomResult
GO
The actual shuffle function is as following:
CREATE FUNCTION shuffle(#string NVARCHAR(MAX))
RETURNS NVARCHAR(MAX) AS
BEGIN
DECLARE #pos INT
DECLARE #char CHAR(1)
DECLARE #shuffeld NVARCHAR(MAX)
DECLARE #random DECIMAL(18,18)
WHILE LEN(#string) > 0
BEGIN
SELECT #random = randomResult FROM randomView
SET #pos = (CONVERT(INT, #random*1000000) % LEN(#string)) + 1
SET #char = SUBSTRING(#string, #pos, 1)
SET #shuffeld = CONCAT(#shuffeld, #char)
SET #string = CONCAT(SUBSTRING(#string, 1, #pos-1), SUBSTRING(#string, #pos+1, LEN(#string)))
END
RETURN #shuffeld
END
Calling the function
DECLARE #string NVARCHAR(MAX) = 'abcdefghijklmnonpqrstuvwxyz0123456789!"ยง$%&/()='
SELECT dbo.shuffle(#string)
There is nothing in standard SQL - your best bet is probably to write a user defined function
We need to figure out a quick and fairly accurate method for point-in-polygon for lat/long values and polygons over google maps. After some research - came across some posts about mysql geometric extensions, and did implement that too -
SELECT id, Contains( PolyFromText( 'POLYGON(".$polygonpath.")' ) , PointFromText( concat( \"POINT(\", latitude, \" \", longitude, \")\" ) ) ) AS
CONTAINS
FROM tbl_points
That did not however work with polygons made up of a large number of points :(
After some more research - came across a standard algorithm called the Ray-casting algorithm but before trying developing a query for that in MySQL, wanted to take my chances if someone had already been through that or came across a useful link which shows how to implement the algorithm in MySQL / SQL-server.
So, cutting it short - question is:
Can anyone please provide the MySQL/SQL-server implementation of Ray casting algorithm?
Additional detail:
Polygons are either of concave, convex or complex.
Targeting quick execution over 100% accuracy.
The following function (MYSQL version of Raycasting algorithm) rocked my world :
CREATE FUNCTION myWithin(p POINT, poly POLYGON) RETURNS INT(1) DETERMINISTIC
BEGIN
DECLARE n INT DEFAULT 0;
DECLARE pX DECIMAL(9,6);
DECLARE pY DECIMAL(9,6);
DECLARE ls LINESTRING;
DECLARE poly1 POINT;
DECLARE poly1X DECIMAL(9,6);
DECLARE poly1Y DECIMAL(9,6);
DECLARE poly2 POINT;
DECLARE poly2X DECIMAL(9,6);
DECLARE poly2Y DECIMAL(9,6);
DECLARE i INT DEFAULT 0;
DECLARE result INT(1) DEFAULT 0;
SET pX = X(p);
SET pY = Y(p);
SET ls = ExteriorRing(poly);
SET poly2 = EndPoint(ls);
SET poly2X = X(poly2);
SET poly2Y = Y(poly2);
SET n = NumPoints(ls);
WHILE i<n DO
SET poly1 = PointN(ls, (i+1));
SET poly1X = X(poly1);
SET poly1Y = Y(poly1);
IF ( ( ( ( poly1X <= pX ) && ( pX < poly2X ) ) || ( ( poly2X <= pX ) && ( pX < poly1X ) ) ) && ( pY > ( poly2Y - poly1Y ) * ( pX - poly1X ) / ( poly2X - poly1X ) + poly1Y ) ) THEN
SET result = !result;
END IF;
SET poly2X = poly1X;
SET poly2Y = poly1Y;
SET i = i + 1;
END WHILE;
RETURN result;
End;
Add
DELIMITER ;;
before the function as required.
The usage for the function is:
SELECT myWithin(point, polygon) as result;
where
point = Point(lat,lng)
polygon = Polygon(lat1 lng1, lat2 lng2, lat3 lng3, .... latn lngn, lat1 lng1)
Please note that the polygon ought to be closed (normally it is closed if you're retrieving a standard kml or googlemap data but just make sure it is - note lat1 lng1 set is repeated at the end)
I did not have points and polygons in my database as geometric fields, so I had to do something like:
Select myWithin(PointFromText( concat( "POINT(", latitude, " ", longitude, ")" ) ),PolyFromText( 'POLYGON((lat1 lng1, ..... latn lngn, lat1 lng1))' ) ) as result
I hope this might help someone.
I would write a custom UDF that implements the ray-casting algorithm in C or Delphi or whatever high level tool you use:
Links for writing a UDF
Here's sourcecode for a MySQL gis implementation that looks up point on a sphere (use it as a template to see how to interact with the spatial datatypes in MySQL).
http://www.lenzg.net/archives/220-New-UDF-for-MySQL-5.1-provides-GIS-functions-distance_sphere-and-distance_spheroid.html
From the MySQL manual:
http://dev.mysql.com/doc/refman/5.0/en/adding-functions.html
UDF tutorial for MS Visual C++
http://rpbouman.blogspot.com/2007/09/creating-mysql-udfs-with-microsoft.html
UDF tutorial in Delphi:
Creating a UDF for MySQL in Delphi
Source-code regarding the ray-casting algorithm
Pseudo-code: http://rosettacode.org/wiki/Ray-casting_algorithm
Article in drDobbs (note the link to code at the top of the article): http://drdobbs.com/cpp/184409586
Delphi (actually FreePascal): http://www.cabiatl.com/mricro/raycast/
Just in case, one MySQL function which accepts MULTIPOLYGON as an input: http://forums.mysql.com/read.php?23,286574,286574
DELIMITER $$
CREATE DEFINER=`root`#`localhost` FUNCTION `GISWithin`(pt POINT, mp MULTIPOLYGON) RETURNS int(1)
DETERMINISTIC
BEGIN
DECLARE str, xy TEXT;
DECLARE x, y, p1x, p1y, p2x, p2y, m, xinters DECIMAL(16, 13) DEFAULT 0;
DECLARE counter INT DEFAULT 0;
DECLARE p, pb, pe INT DEFAULT 0;
SELECT MBRWithin(pt, mp) INTO p;
IF p != 1 OR ISNULL(p) THEN
RETURN p;
END IF;
SELECT X(pt), Y(pt), ASTEXT(mp) INTO x, y, str;
SET str = REPLACE(str, 'POLYGON((','');
SET str = REPLACE(str, '))', '');
SET str = CONCAT(str, ',');
SET pb = 1;
SET pe = LOCATE(',', str);
SET xy = SUBSTRING(str, pb, pe - pb);
SET p = INSTR(xy, ' ');
SET p1x = SUBSTRING(xy, 1, p - 1);
SET p1y = SUBSTRING(xy, p + 1);
SET str = CONCAT(str, xy, ',');
WHILE pe > 0 DO
SET xy = SUBSTRING(str, pb, pe - pb);
SET p = INSTR(xy, ' ');
SET p2x = SUBSTRING(xy, 1, p - 1);
SET p2y = SUBSTRING(xy, p + 1);
IF p1y < p2y THEN SET m = p1y; ELSE SET m = p2y; END IF;
IF y > m THEN
IF p1y > p2y THEN SET m = p1y; ELSE SET m = p2y; END IF;
IF y <= m THEN
IF p1x > p2x THEN SET m = p1x; ELSE SET m = p2x; END IF;
IF x <= m THEN
IF p1y != p2y THEN
SET xinters = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x;
END IF;
IF p1x = p2x OR x <= xinters THEN
SET counter = counter + 1;
END IF;
END IF;
END IF;
END IF;
SET p1x = p2x;
SET p1y = p2y;
SET pb = pe + 1;
SET pe = LOCATE(',', str, pb);
END WHILE;
RETURN counter % 2;
END
In reply to zarun function for finding lat/long within polygon.
I had a property table having lat/long information. So I had to get the records whose lat/long lies within polygon lats/longs (which I got from Google API). At first I was dumb how to use the Zarun function. So here is the solution query for it.
Table: properties
Fields: id, latitude, longitude, beds etc...
Query:
SELECT id
FROM properties
WHERE myWithin(
PointFromText(concat( "POINT(", latitude, " ", longitude, ")")),
PolyFromText('POLYGON((37.628134 -77.458334,37.629867 -77.449021,37.62324 -77.445416,37.622424 -77.457819,37.628134 -77.458334))' )
) = 1 limit 0,50;
Hope it will save time for dumbs like me ;)
I wanted to use the above mywithin stored procedure on a table of polygons so here are the commands to do just that.
After importing a shapefile containing polygons into mysql using ogr2ogr as follows
ogr2ogr -f "mysql" MYSQL:"mydbname,host=localhost,user=root,password=mypassword,port=3306" -nln "mytablename" -a_srs "EPSG:4326" /path/to/shapefile.shp
you can then use MBRwithin to prefilter your table and mywithin to finish as follows
DROP TEMPORARY TABLE IF EXISTS POSSIBLE_POLYS;
CREATE TEMPORARY TABLE POSSIBLE_POLYS(OGR_FID INT,SHAPE POLYGON);
INSERT INTO POSSIBLE_POLYS (OGR_FID, SHAPE) SELECT mytablename.OGR_FID,mytablename.SHAPE FROM mytablename WHERE MBRwithin(#testpoint,mytablename.SHAPE);
DROP TEMPORARY TABLE IF EXISTS DEFINITE_POLY;
CREATE TEMPORARY TABLE DEFINITE_POLY(OGR_FID INT,SHAPE POLYGON);
INSERT INTO DEFINITE_POLY (OGR_FID, SHAPE) SELECT POSSIBLE_POLYS.OGR_FID,POSSIBLE_POLYS.SHAPE FROM POSSIBLE_POLYS WHERE mywithin(#testpoint,POSSIBLE_POLYS.SHAPE);
where #testpoint is created, for example, from
SET #longitude=120;
SET #latitude=-30;
SET #testpoint =(PointFromText( concat( "POINT(", #longitude, " ", #latitude, ")" ) ));
It is now a Spatial Extension as of MySQL5.6.1 and above. See function_st-contains at Docs.
Here is a version that works with MULTIPOLYGONS (an adaptation of Zarun's one which only works for POLYGONS).
CREATE FUNCTION GISWithin(p POINT, multipoly MULTIPOLYGON) RETURNS INT(1) DETERMINISTIC
BEGIN
DECLARE n,i,m,x INT DEFAULT 0;
DECLARE pX,pY,poly1X,poly1Y,poly2X,poly2Y DECIMAL(13,10);
DECLARE ls LINESTRING;
DECLARE poly MULTIPOLYGON;
DECLARE poly1,poly2 POINT;
DECLARE result INT(1) DEFAULT 0;
SET pX = X(p);
SET pY = Y(p);
SET m = NumGeometries(multipoly);
WHILE x<m DO
SET poly = GeometryN(multipoly,x);
SET ls = ExteriorRing(poly);
SET poly2 = EndPoint(ls);
SET poly2X = X(poly2);
SET poly2Y = Y(poly2);
SET n = NumPoints(ls);
WHILE i<n DO
SET poly1 = PointN(ls, (i+1));
SET poly1X = X(poly1);
SET poly1Y = Y(poly1);
IF ( ( ( ( poly1X <= pX ) && ( pX < poly2X ) ) || ( ( poly2X <= pX ) && ( pX < poly1X ) ) ) && ( pY > ( poly2Y - poly1Y ) * ( pX - poly1X ) / ( poly2X - poly1X ) + poly1Y ) ) THEN
SET result = !result;
END IF;
SET poly2X = poly1X;
SET poly2Y = poly1Y;
SET i = i + 1;
END WHILE;
SET x = x + 1;
END WHILE;
RETURN result;
End;
I've tried a tutorial from this site, where a sample table gets inserted along with some testing data in a stored procedure. But unfortunately an error message is thrown, saying there's something wrong with the DELIMITER. The whole script is:
CREATE TABLE filler (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT
) ENGINE=Memory;
CREATE TABLE t_hierarchy (
id INT NOT NULL PRIMARY KEY,
parent INT NOT NULL,
lft INT NOT NULL,
rgt INT NOT NULL,
sets LineString NOT NULL,
data VARCHAR(100) NOT NULL,
stuffing VARCHAR(100) NOT NULL
) ENGINE=MyISAM;
DELIMITER $$
CREATE PROCEDURE prc_filler(cnt INT)
BEGIN
DECLARE _cnt INT;
SET _cnt = 1;
WHILE _cnt <= cnt DO
INSERT
INTO filler
SELECT _cnt;
SET _cnt = _cnt + 1;
END WHILE;
END;
CREATE PROCEDURE prc_hierarchy(width INT)
main:BEGIN
DECLARE last INT;
DECLARE level INT;
SET last = 0;
SET level = 0;
WHILE width >= 1 DO
INSERT
INTO t_hierarchy
SELECT COALESCE(h.id, 0) * 5 + f.id,
COALESCE(h.id, 0),
COALESCE(h.lft, 0) + 1 + (f.id - 1) * width,
COALESCE(h.lft, 0) + f.id * width,
LineString(
Point(-1, COALESCE(h.lft, 0) + 1 + (f.id - 1) * width),
Point(1, COALESCE(h.lft, 0) + f.id * width)
),
CONCAT('Value ', COALESCE(h.id, 0) * 5 + f.id),
RPAD('', 100, '*')
FROM filler f
LEFT JOIN
t_hierarchy h
ON h.id >= last;
SET width = width / 5;
SET last = last + POWER(5, level);
SET level = level + 1;
END WHILE;
END
$$
DELIMITER ;
START TRANSACTION;
CALL prc_filler(5);
CALL prc_hierarchy(585937);
COMMIT;
CREATE INDEX ix_hierarchy_parent ON t_hierarchy (parent);
CREATE INDEX ix_hierarchy_lft ON t_hierarchy (lft);
CREATE INDEX ix_hierarchy_rgt ON t_hierarchy (rgt);
CREATE SPATIAL INDEX sx_hierarchy_sets ON t_hierarchy (sets);
Executing this on a MySQL 5.0.51a-24+lenny2 server gives me the following error message:
[Err] 1310 - End-label $$ without match
Does anyone know why this occurs and how to fix it?
$$ should be after every created procedure.
CREATE PROCEDURE prc_filler(cnt INT)
BEGIN
DECLARE _cnt INT;
SET _cnt = 1;
WHILE _cnt <= cnt DO
INSERT
INTO filler
SELECT _cnt;
SET _cnt = _cnt + 1;
END WHILE;
END$$ -- here's your problem
That's why it is called delimiter - it separates SQL commands. You have ';' inside procedures and '$$' outside of them.
I have seen some MySQL clients that don't support the DELIMITER keyword and throw an error... I believe it is your case... You should try to use a different client, maybe the CLI tool bundled with MySQL (if you can...)?