MySQL implementation of ray-casting Algorithm? - mysql

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;

Related

I am getting an error near the end of my function and the only hint is that it is near ''

I've tried reading several other similar answers to this, but none of them as far as I can tell are fixed the same way. I've tried deleting all new lines/tabs to try to remove invisible characters and even just putting it on one line.
I'm not the best at SQL once you get past a certain point, so this problem just me not realizing something, but any help would be appreciated.
The exact error is #1064 - You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '' at line 66
and line 66 is RETURN points; right above the END;;
DELIMITER ;;
CREATE FUNCTION getPointValue(run_id INT)
RETURNS INT
BEGIN
DECLARE points INT DEFAULT 0;
DECLARE total_completions INT DEFAULT 1;
DECLARE place INT;
DECLARE time FLOAT(12,4);
DECLARE style INT;
DECLARE run_type INT;
DECLARE percentile FLOAT(12,4);
DECLARE total_points INT DEFAULT 3000;
DECLARE percentile_potential INT;
DECLARE temp FLOAT;
DECLARE bracket_min INT DEFAULT 0;
DECLARE bracket_max INT DEFAULT 0;
SELECT time INTO time FROM surf_run WHERE run_id = run_id;
SELECT style INTO style FROM surf_run WHERE run_id = run_id;
SELECT run_type INTO run_type FROM surf_run WHERE run_id = run_id;
SELECT COUNT(*) INTO place FROM surf_run WHERE time <= time AND style = style AND run_type = run_type AND best_run = TRUE;
SELECT COUNT(*) INTO total_completions FROM surf_run WHERE style = style AND run_type = run_type AND best = TRUE;
IF place = 1 THEN
SET points = 10000;
ELSE IF place = 2 THEN
SET points = 7500;
ELSE IF place = 3 THEN
SET points = 5000;
ELSE IF place = 4 THEN
SET points = 3500;
ELSE IF place = 5 THEN
SET points = 2500;
ELSE
SET percentile = (place / total_completions);
IF percentile <= 0.05 THEN
SET percentile_potential = 3000;
SET points = 2000;
SET bracket_min = 0;
SET bracket_max = total_completions * 0.05;
ELSE IF percentile <= 0.10 THEN
SET bracket_max = total_completions * 0.05 + 1;
SET bracket_max = total_completions * 0.1;
SET percentile_potential = 2000;
SET points = 1250;
ELSE IF percentile <= 0.15 THEN
SET bracket_max = total_completions * 0.1 + 1;
SET bracket_max = total_completions * 0.15;
SET percentile_potential = 1250;
SET points = 750;
ELSE IF percentile <= 0.25 THEN
SET bracket_max = total_completions * 0.15 + 1;
SET bracket_max = total_completions * 0.25;
SET percentile_potential = 750;
SET points = 10;
END IF;
SET points = points + GREATEST(0, ROUND(percentile_potential - percentile_potential * LOG(place, bracket_max)));
END IF;
RETURN points;
END;;
DELIMITER ;
Common "beauty" syntax error.
You use ELSE IF - in this case IF is not an alternative branch but separate IF statement. Hence you obtain multiple IF statements without END IF which produces an error.
Investigate MySQL 8.0 Reference Manual / ... / IF Statement - ELSEIF does not contain/allow space char.
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=a4450a40ab690e82ffa18bb3978e68c0

Approximate distance between one lat/long coordinate and a fixed one using SQL

Here
Distance between two lat,lon points
I found a very simple and nice formula:
d = sqrt(pow(lat2-lat1, 2) + cos(lat1)*pow(lon2-lon1, 2))
which I am using like this:
SELECT name, SQRT(POW(lat-45, 2) + COS(45)*POW(lon-8, 2)) AS distance
FROM shop
ORDER BY distance
and everything worked fine, because I only needed to get an approximate RELATIVE distance.
But then I needed an ABSOLUTE distance, even if approximate... And I started to realize that maybe "d" cannot be converted in meters/miles because e.g. there are 180 degrees of latitude but 360 of longitude, so this almost-Pythagorean theorem is "stretching" the diagonal line in a way that messes up a possible absolute calculation... After a while, I gave up and... Here I am.
How can I convert "d" to an absolute distance in meters/miles?
PS: Yes, I will precalculate all the constants in the SQL query in the final version...
this worked for me:
$sql = "SELECT *,(((acos(sin((".$latitude."*pi()/180)) * sin((Latitude*pi()/180))+cos((".$latitude."*pi()/180)) * cos((Latitude*pi()/180)) * cos(((".$longitude."- Longitude)*pi()/180))))*180/pi())*60*1.1515*1.609344) as distance
FROM shops WHERE distance >= ".$distance; //distance is in Km
also take a look google maps functions:
https://developers.google.com/maps/articles/phpsqlsearch_v3?csw=1#findnearsql
In SQL for get the distance in metters check this query, also you can convert it to a function
declare #lat1 FLOAT
declare #lat2 FLOAT
declare #lon1 FLOAT
declare #lon2 FLOAT
SET #lat1 = 18.4768047
SET #lon1 = -69.9516518
SET #lat2 = 18.4624464
SET #lon2 = -69.9652686
select (ACOS(SIN(PI()*#lat1/180.0)*SIN(PI()*#lat2/180.0)+COS(PI()*#lat1/180.0)*COS(PI()*#lat2/180.0)*COS(PI()*#lon2/180.0-PI()*#lon1/180.0))*6371)*1000
This is an example
UPDATE!!!
I create this procedure for MySQL for get KM, Miles, Metters and yards
DELIMITER //
CREATE PROCEDURE sp_Distance(lat1 FLOAT,lon1 FLOAT,lat2 FLOAT,lon2 FLOAT,unit INT)
BEGIN
declare KM INT;
declare MILE INT;
declare METTER INT;
declare YARD INT;
SET KM = 6371;
SET MILE = 3959;
SET METTER = KM*1000;
SET YARD = MILE*1760;
SELECT ((ACOS(SIN(PI()*lat1/180.0)*SIN(PI()*lat2/180.0)+
COS(PI()*lat1/180.0)*COS(PI()*lat2/180.0)*COS(PI()*lon2/180.0-PI()*lon1/180.0)))*
(SELECT CASE
WHEN unit = 1 THEN KM
WHEN unit = 2 THEN MILE
WHEN unit = 3 THEN METTER
WHEN unit = 4 THEN YARD END as unit_value)
) as Distance;
END;//
DELIMITER ;
Call with:
call sp_Distance(18.4768047,-69.9516518,18.4624464,-69.9652686,1)
And for SQL
CREATE PROCEDURE sp_Distance(#lat1 FLOAT,#lon1 FLOAT,#lat2 FLOAT,#lon2 FLOAT,#unit INT)
AS
BEGIN
declare #KM INT
declare #MILE INT
declare #METTER INT
declare #YARD INT
SET #KM = 6371
SET #MILE = 3959
SET #METTER = #KM*1000
SET #YARD = #MILE*1760
SELECT ((ACOS(SIN(PI()*#lat1/180.0)*SIN(PI()*#lat2/180.0)+
COS(PI()*#lat1/180.0)*COS(PI()*#lat2/180.0)*COS(PI()*#lon2/180.0-PI()*#lon1/180.0)))*
(SELECT CASE
WHEN #unit = 1 THEN #KM
WHEN #unit = 2 THEN #MILE
WHEN #unit = 3 THEN #METTER
WHEN #unit = 4 THEN #YARD END as unit_value)
) as Distance
END
Call with:
exec sp_Distance 18.4768047,-69.9516518,18.4624464,-69.9652686,1

MySQL convert Degree, Minutes, Seconds to Degree decimal

I have multiple rows of Degrees Minutes Seconds that I need to convert with a query.
36°19'11.46" N = 36.31985
95°36'02.22" W = 95.600617
Each row is going to be different. I've been stuck on this for two days. Is this even possible?
Nice lifehack: reverse problem solution (degree to DMS) using SEC_TO_TIME built-in MySQL function:
CREATE FUNCTION `geocoords`(lon double, lat double) RETURNS varchar(24) CHARSET cp1251
NO SQL
DETERMINISTIC
begin
declare alon double;
declare alat double;
declare slon varchar(12);
declare slat varchar(12);
set alon = abs(lon);
set alat = abs(lat);
set slon = TIME_FORMAT(SEC_TO_TIME(alon*3600), '%H°%i''%s"');
set slat = TIME_FORMAT(SEC_TO_TIME(alat*3600), '%H°%i''%s"');
if lon>0 then
set slon = concat(slon, 'E');
elseif lon<0 then
set slon = concat(slon, 'W');
end if;
if lat>0 then
set slat = concat(slat, 'N');
elseif lat<0 then
set slat = concat(slat, 'S');
end if;
return concat(slat, ' ', slon);
end
SELECT geocoords(30.550157546997, 50.344024658203)
50°20'38"N 30°33'01"E
The following should work:
SELECT D + M/60 + S/3600;
For example, in MySQL:
SELECT 36 + 19/60 + 11.46/3600;
returns: 36.319850
I ended up building this, and it worked flawlessly with what I needed. You will note that I added a C to the numbers, this is to flag them, so if it had already been converted it wouldn't continue to convert.
UPDATE
MasterTable
SET
MasterTable.Latitude_A = MasterTable.Latitude,
MasterTable.Longitude_A = MasterTable.Longitude
WHERE
ProjectID = 'ProjectAlpha'
and Sequence = '0'
and MasterTable.Latitude NOT LIKE '%C%'
and MasterTable.Longitude NOT LIKE '%C%';
TRUNCATE TABLE gpsconvert;
INSERT into gpsconvert(gpsconvert.`Account Number`,gpsconvert.Latitude,gpsconvert.Longitude)
SELECT
MasterTable.AccountNumber,
MasterTable.Latitude,
MasterTable.Longitude
FROM
MasterTable
WHERE
MasterTable.ProjectID = 'ProjectAlpha'
and MasterTable.Sequence = '0'
and MasterTable.Latitude NOT LIKE '%c%'
and MasterTable.Longitude NOT LIKE '%c%'
and MasterTable.Latitude <> ''
and MasterTable.Longitude <> '';
UPDATE
gpsconvert
SET
gpsconvert.LatDegree = LEFT(gpsconvert.Latitude,2),
gpsconvert.LatMinutes = SUBSTRING(gpsconvert.Latitude,-7,2),
gpsconvert.LatSeconds = SUBSTRING(gpsconvert.latitude,-5,5),
gpsconvert.LatDecimal = gpsconvert.LatDegree + (gpsconvert.LatMinutes/60) + (gpsconvert.LatSeconds/3600),
gpsconvert.LongDegree = LEFT(gpsconvert.Longitude,2),
gpsconvert.LongMinutes = SUBSTRING(gpsconvert.Longitude,-7,2),
gpsconvert.LongSeconds = SUBSTRING(gpsconvert.Longitude,-5,5),
gpsconvert.LongDecimal = gpsconvert.LongDegree + (gpsconvert.LongMinutes/60) + (gpsconvert.LongSeconds/3600);
UPDATE
MasterTable
INNER JOIN
gpsconvert on gpsconvert.`Account Number` = MasterTable.AccountNumber
SET
MasterTable.Latitude = CONCAT(gpsconvert.LatDecimal,'c'),
MasterTable.Longitude = CONCAT(gpsconvert.LongDecimal,'c')
WHERE
MasterTable.ProjectID = 'ProjectAlpha'
and MasterTable.Sequence = '0'
and MasterTable.AccountNumber = gpsconvert.`Account Number`
This converts strings in the form 21°48'6.384" to decimal
CREATE FUNCTION database.DMS_TO_DECIMAL(dms varchar(20))
RETURNS decimal(12,9)
BEGIN
DECLARE deg decimal(12,9);
DECLARE mins decimal(12,9);
DECLARE secs decimal(12,9);
SET deg = CAST( SUBSTRING_INDEX(dms, '°', 1) AS decimal(12,9));
SET mins = CAST( (SUBSTR(dms, POSITION('°' IN dms) + 1, POSITION("'" IN dms) - POSITION('°' IN dms) - 1)) AS decimal(12,9));
SET secs = CAST( (SUBSTR(dms, POSITION("'" IN dms) + 1, POSITION("\"" IN dms) - POSITION("'" IN dms) - 1)) AS decimal(12,9));
RETURN deg + mins/60 + secs/3600;
END
Add orientation (W/S):
DROP FUNCTION IF EXISTS `DmsToDecimal`;
DELIMITER $$
CREATE FUNCTION `DmsToDecimal`(`dms` VARCHAR(50))
RETURNS DECIMAL(12,9)
DETERMINISTIC
NO SQL
SQL SECURITY INVOKER
BEGIN
DECLARE deg decimal(12,9);
DECLARE mins decimal(12,9);
DECLARE secs decimal(12,9);
DECLARE sign integer;
SET dms = UPPER(TRIM(REPLACE(dms, ",", ".")));
SET deg = CAST( SUBSTRING_INDEX(dms, '°', 1) AS decimal(12,9));
SET mins = CAST( (SUBSTR(dms, POSITION('°' IN dms) + 1, POSITION("'" IN dms) - POSITION("°" IN dms) - 1)) AS decimal(12,9));
SET secs = CAST( (SUBSTR(dms, POSITION("'" IN dms) + 1, POSITION("""" IN dms) - POSITION("'" IN dms) - 1)) AS decimal(12,9));
SET sign = 1 - 2 * (RIGHT(dms, 1) = "W" OR RIGHT(dms, 1) = "S";
RETURN sign * (deg + mins / 60 + secs / 3600);
END$$
DELIMITER ;

What is the error for this MySQL function for lat-lng distance?

I am trying to insert a function that I found on the web into my MySQL database that should calculate the distance between 2 points given their respective coordinates.
I found the function here: http://www.sqlexamples.info/SPAT/mysql_distance.htm
This is what I'm trying to insert into MySQL:
USE city_notifier; /*it is the name of my database*/
DROP FUNCTION IF EXISTS fn_distance;
CREATE FUNCTION fn_distance
(p_x1 FLOAT, p_y1 FLOAT, p_x2 FLOAT, p_y2 FLOAT)
RETURNS FLOAT
DETERMINISTIC
BEGIN
DECLARE v_dist FLOAT;
DECLARE A FLOAT; DECLARE B FLOAT;
DECLARE C FLOAT; DECLARE D FLOAT;
/*
returns distance calculation between two points in
LAT-LONG coordinates
*/
SET v_dist = 0;
-- convert to radians
SET A = p_x1 / 57.29577951;
SET B = p_y1 / 57.29577951;
SET C = p_x2 / 57.29577951;
SET D = p_y2 / 57.29577951;
IF (A = C && B = D) THEN
SET v_dist = 0;
ELSEIF ((sin(A)*sin(C)+cos(A)*cos(C)*cos(B - D)) > 1) THEN
SET v_dist = 3963.1 * acos(1);
ELSE
SET v_dist = 3963.1*acos(sin(A)*sin(C) + cos(A)*cos(C)*cos(B - D));
END IF;
SET v_dist = v_dist * 1.609;
/* return distance in km. */
RETURN v_dist;
END;
It continues to give me an error: #1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 6.
But I cannot find any error in the function.
Can you guys help me please? What is wrong with that function?
This should work :
USE city_notifier; /*it is the name of my database*/
DROP FUNCTION IF EXISTS fn_distance;
DELIMITER $$
CREATE FUNCTION fn_distance
(p_x1 FLOAT, p_y1 FLOAT, p_x2 FLOAT, p_y2 FLOAT)
RETURNS FLOAT
DETERMINISTIC
BEGIN
DECLARE v_dist FLOAT;
DECLARE A FLOAT; DECLARE B FLOAT;
DECLARE C FLOAT; DECLARE D FLOAT;
/*
returns distance calculation between two points in
LAT-LONG coordinates
*/
SET v_dist = 0;
-- convert to radians
SET A = p_x1 / 57.29577951;
SET B = p_y1 / 57.29577951;
SET C = p_x2 / 57.29577951;
SET D = p_y2 / 57.29577951;
IF (A = C && B = D) THEN
SET v_dist = 0;
ELSEIF ((sin(A)*sin(C)+cos(A)*cos(C)*cos(B - D)) > 1) THEN
SET v_dist = 3963.1 * acos(1);
ELSE
SET v_dist = 3963.1*acos(sin(A)*sin(C) + cos(A)*cos(C)*cos(B - D));
END IF;
SET v_dist = v_dist * 1.609;
/* return distance in km. */
RETURN v_dist;
END$$
DELIMITER ;
i think you should try
IF ((A = C) && (B = D)) THEN
to make it work.

MySQL: How do I combine a Stored procedure with another Function?

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