I'm new to GIS in MySQL and it's melting my brain!
I've created a table "places" as so:
CREATE TABLE `places` (
`id` int(6) unsigned zerofill NOT NULL auto_increment,
`business_name` varchar(100) NOT NULL,
`street_postcode` varchar(10) NOT NULL,
`longitude` decimal(22,20) NOT NULL,
`latitude` decimal(22,20) NOT NULL,
`coord` point NOT NULL,
UNIQUE KEY `id` (`id`),
) ENGINE=InnoDB AUTO_INCREMENT=25080 DEFAULT CHARSET=utf8
id, business_name and street_postcode where inserted into the table
I then updated the table with longitude and latitude coordinates in PHP as so:
UPDATE `places` SET `longitude` = '".$longitude."', `latitude` = '".$latitude."', `coord` = GeomFromText('POINT(".$coord.")') WHERE `id` = '".$row->id."' LIMIT 1
This all seems to have gone well, but I'm stuck when trying to find the nearest place to X coordinates. How do I find the 10 nearest places to X longitude and latitude?
This does not seem to work:
SELECT business_name, street_postcode, ROUND(GLength(LineStringFromWKB(LineString(AsBinary(coord), AsBinary('51.49437081 -0.2275573')))))
AS distance FROM places ORDER BY distance ASC LIMIT 10;
Eternal thanks in advance!
That is because the spatial functions are not really implmented in MySQL. They stubbed out all the code but have not implemented all the methods.
I recommend using PostGIS or SpatiaLite.
Related
First of all, I am using mysql8.
I have a table that store states of my country.
My table ddl is:
CREATE TABLE state_v2(
`id` int(11) NOT NULL,
uf_code INT NULL,
uf VARCHAR(2) NOT NULL,
`name` VARCHAR(100) NOT NULL,
latitude FLOAT(8) NOT NULL,
longitude FLOAT(8) NOT NULL,
`country` varchar(75) NOT NULL,
coord POINT SRID 4326 NOT NULL,
PRIMARY KEY (id),
unique (country, uf_code),
unique (latitude, longitude),
index (uf_code)
);
ALTER TABLE state_v2 ADD SPATIAL INDEX(coord);
The user choose a place and I use this place as reference to find the nearest state less then 100km. (the parameter is a correct point).
I am using this query:
SELECT * FROM (
SELECT
sv.*,
ST_distance_sphere(
$param,
sv.coord) as distance
FROM state_v2 sv
WHERE ST_distance_sphere(
$param,
sv.coord) < 100000
) as temp
order by temp.distance;
1 - Is it correct this query? I am worried about the performance of using like that, it is using full table scan.
2 - Is it correct the created indexes?
I currently am doing this to get some data from our table:
SELECT DISTINCT(CategoryID),Distance FROM glinks_DistancesForTowns WHERE LinkID = $linkID ORDER BY Distance LIMIT 20
I'm iterating over that for every link id we have (50k odd). Them I'm processing them in Perl with:
my #cats;
while (my ($catid,$distance) = $sth->fetchrow) {
push #cats, $cat;
}
I'm trying to see if there is a better way to do this in a sub-query with MySQL, vs doing 50k smaller queries (i.e one per link)
The basic structure of the table is:
glinks_Links
ID
glinks_DistancesForTowns
LinkID
CategoryID
Distance
I'm sure there must be a simple way to do it - but I'm just not seeing it.
As requested - here is a dump of the table structure. Its actually more complex than that, but the other fields just hold values so I've taken those bits out to give a cleaner over-view of the structure:
CREATE TABLE `glinks_DistancesForTowns` (
`LinkID` int(11) DEFAULT NULL,
`CategoryID` int(11) DEFAULT NULL,
`Distance` float DEFAULT NULL,
`isPaid` int(11) DEFAULT NULL,
KEY `LinkID` (`LinkID`),
KEY `CategoryID` (`CategoryID`,`isPaid`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
CREATE TABLE `glinks_Links` (
`ID` int(10) unsigned NOT NULL AUTO_INCREMENT,
`Title` varchar(100) NOT NULL DEFAULT '',
`URL` varchar(255) NOT NULL DEFAULT 'http://',
PRIMARY KEY (`ID`),
KEY `booking_hotel_id_fk` (`booking_hotel_id_fk`)
) ENGINE=MyISAM AUTO_INCREMENT=617547 DEFAULT CHARSET=latin1
This is the kind of thing I'm hoping for:
SELECT glinks_Links.ID FROM glinks_Links as links, glinks_DistancesForTowns as distance (
SELECT DISTINCT(CategoryID),Distance FROM distance WHERE distance.LinkID = links.ID ORDER BY Distance LIMIT 20
)
But obviously that doesn't work;)
It sounds like you want the top 20 towns by distance for each link, right?
MySQL 8.0 supports window functions, and this would be the way to write the query:
WITH cte AS (
SELECT l.ID, ROW_NUMBER() OVER(PARTITION BY l.ID ORDER BY d.Distance) AS rownum
FROM glinks_Links as l
JOIN glinks_DistancesForTowns AS d ON d.LinkID = l.ID
) SELECT ID FROM cte WHERE rownum <= 20;
Versions older than 8.0 do not support these features of SQL, so you have to get creative with user-defined variables or self-joins. See for example my answer to How to SELECT the newest four items per category?
This is slow and taking 1.003 seconds
set #POSTCODE = 'SL2 1AT';
select latitude, longitude from postcode.postcodelatlng where postcode = #POSTCODE;
This is fast and taking .127 seconds
select latitude, longitude from postcode.postcodelatlng where postcode = 'SL2 1AT';
Table create statement
CREATE TABLE `postcodelatlng` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`postcode` varchar(8) NOT NULL,
`latitude` decimal(18,15) NOT NULL,
`longitude` decimal(18,15) NOT NULL,
PRIMARY KEY (`id`),
KEY `id_postcode` (`postcode`),
KEY `latLong` (`latitude`,`longitude`) USING BTREE
) ENGINE=MyISAM AUTO_INCREMENT=1738245 DEFAULT CHARSET=latin1;
Could you please help, why this is happening and we can we do to resolve this.
first of all, I suggest you use InnoDB over MyISAM.
The compare to a latin1 field was incurring a fairly heavy performance overhead.
instead of you can use following.
SET #POSTCODE:= CONVERT('SL2 1AT' USING latin1);
If you client is utf8 but your table is latin1.Now the query runs as fast as I would expect.
:= is the assignment operator in MySQL
set #POSTCODE := 'SL2 1AT';
select latitude, longitude from postcode.postcodelatlng where postcode = #POSTCODE;
There is a colon missing in the first line
I am measuring some values over a geo-location
I need to create a table that consists of:
date
longitude
latitude
value
The date column is the date of the measure,
The value is just an int value of the measure,
the longitude and latitude are a coordinates - they are floating points columns with 3 digits before the point and 5 after (i.e. *.*)
I'm wondering how to define my table, I try to use:
CREATE TABLE `obs` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`date` date DEFAULT NULL,
`lon` decimal(5,5) DEFAULT NULL,
`lat` decimal(5,5) DEFAULT NULL,
`val` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Here is a link to sqlFiddle
But When I'm trying to run
INSERT INTO `obs` VALUES (null,'2014/06/07','34.000','31.342',1)
I am getting the following error:
Out of range value for column 'lon' at row 1:
Can anyone explain me what's wrong?
decimal(5,5) means
5 decimal places in TOTAL
5 decimal places after the point
That would make all numbers invalid having a decimal place before the point.
You probably want
decimal(10,5)
In mysql I have a varchar containing latitude and longitude provided by Google maps.
I need to be able to query based on a bounding box value, but have no need for the geo features now available. I'm trying to populate 2 new Decimal fields with the Decimal values found in the varchar. Here is the query that I'm trying to use, but the result are all rounded values in the new fields.
Sample data:
'45.390746926938185, -122.75535710155964',
'45.416444621636415, -122.63058006763458'
Create Table:
CREATE TABLE IF NOT EXISTS `cameras` (
`id` int(11) NOT NULL auto_increment,
`user_id` int(75) NOT NULL,
`position` varchar(75) NOT NULL,
`latitude` decimal(17,15) default NULL,
`longitude` decimal(18,15) default NULL,
`address` varchar(75) NOT NULL,<br />
`date` varchar(11) NOT NULL,<br />
`status` int(1) NOT NULL default '1',
`duplicate_report` int(11) NOT NULL default '0',
`missing_report` int(11) NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1050 ;
SQL:
UPDATE cameras
SET latitude = CAST( (substring(position,1,locate(',',position))) AS DECIMAL(17,15) ),
longitude = CAST( (substring(position,locate(',',position)+1)) AS DECIMAL(18,15) )
SQL Alternate attempt:
UPDATE cameras
SET latitude = CONVERT( (substring(position,1,locate(',',position))), DECIMAL(17,15) ),
longitude = CONVERT( (substring(position,locate(', ',position)+1)), DECIMAL(18,15) )
The resulting field values are for both scenarios:
45.000000000000000 and -122.000000000000000
AND
45.000000000000000 and -122.000000000000000
Can anyone see what I'm doing wrong?
Thanks.
Both the CAST and CONVERT forms seem to be correct.
SELECT CAST((SUBSTRING(t.position,1,LOCATE(',',t.position))) AS DECIMAL(17,15)) AS lat_
, CONVERT(SUBSTRING(t.position,LOCATE(', ',t.position)+1),DECIMAL(18,15)) AS long_
FROM (SELECT '45.390746926938185, -122.75535710155964' AS `position`) t
lat_ long_
------------------ ----------------------
45.390746926938185 -122.755357101559640
I think position is a reserved word, but I don't think that matters in this case. But it wouldn't hurt to assign a table alias and qualify all column references
UPDATE cameras c
SET c.latitude = CAST((SUBSTRING(c.position,1,LOCATE(',',c.position))) AS DECIMAL(17,15))
, c.longitude = CAST((SUBSTRING(c.position,LOCATE(',',c.position)+1)) AS DECIMAL(18,15))
But I suspect that won't resolve the problem.
One thing to check is for a before or after update trigger defined on the table, which is rounding/modifying the values assigned to the latitude and longitude columns?
I suggest you try running just a query.
SELECT CAST((SUBSTRING(c.position,1,LOCATE(',',c.position))) AS DECIMAL(17,15)) AS lat_
, CAST((SUBSTRING(c.position,LOCATE(',',c.position)+1)) AS DECIMAL(18,15)) AS lon_
FROM cameras c
and verify that produces the decimal values you expect.
A dot character should be recognized as a decimal point. Does the position column contain some other special characters, like a space or something?
From what you posted, it looks like the CAST and CONVERT working on the integer portion up to the decimal point. (There shouldn't be an implicit convert to signed integer in there, so it's not clear why the characters following the decimal point aren't being included.)
If you can figure out what character(s) are being used to represent the decimal point, then you could use a MySQL REPLACE() function to replace those with a simple dot character.