Я заметил несколько вещей с исходным кодом, которые были неправильными (использование mysql
, ошибка в parseXml
, поле поиска, которое не используется и ... другие), так как у меня было немного свободного времени.поиграл и собрал несколько иную версию, которая, если ее изучить и адаптировать, должна помочь решить возникшие проблемы.
Пример таблицы той же структуры ......
mysql> describe markers;
+---------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | YES | | NULL | |
| address | varchar(512) | YES | | NULL | |
| lat | float(10,6) | NO | | NULL | |
| lng | float(10,6) | NO | | NULL | |
| type | int(11) | YES | | 1 | |
+---------+--------------+------+-----+---------+----------------+
Пример данных .......
mysql> select * from markers limit 10;
+----+-------------------------+-------------------------------------------------------------+-----------+-----------+------+
| id | name | address | lat | lng | type |
+----+-------------------------+-------------------------------------------------------------+-----------+-----------+------+
| 42 | Nathro 17 x 135m | 1 Goynd Steading, Glenogil, Forfar, Angus DD8 3SW, UK | 56.793251 | -2.862310 | 1 |
| 43 | Ark Hill - 8 x 81m | 3 Dryburn Cottages, Glenogilvy, Forfar, Angus DD8 1UP, UK | 56.570656 | -3.051173 | 1 |
| 44 | Dodd Hill - 5 x 125m | 4 Backmuir Rd, Duntrune, Tealing, Dundee, Angus DD4 0PT, UK | 56.542511 | -2.905154 | 1 |
| 45 | Govals - 6 x 87m | B9127, Forfar, Angus DD8, UK | 56.582321 | -2.950902 | 1 |
| 46 | Carsegownie - 1 x78m | B9134, Forfar, Angus DD8, UK | 56.679512 | -2.806298 | 1 |
| 47 | North Tarbrax - 1 x 45m | 4 Nether Finlarg Farm Cottages, Forfar, Angus DD8 1XQ, UK | 56.571445 | -2.924766 | 1 |
| 48 | The Carrach - 9 x 84m | B951, Kirriemuir, Angus DD8, UK | 56.693844 | -3.131382 | 1 |
| 49 | Glaxo - 2 x 132m | 5 Meridian St, Montrose, Angus DD10 8DS, UK | 56.704315 | -2.466087 | 1 |
| 50 | Craignathro 1 x 24m | Dundee Rd, Forfar, Angus DD8, UK | 56.624084 | -2.884898 | 1 |
| 51 | Craignathro 2 - 1x41m | A90, Forfar, Angus DD8, UK | 56.620872 | -2.880220 | 1 |
+----+-------------------------+-------------------------------------------------------------+-----------+-----------+------+
MySQL Stored Procedure
, который я написал ранее - небольшая модификация для решения этого конкретного вопроса / проблемы.
CREATE PROCEDURE `spFindMarkers`(
IN `param_name` VARCHAR(64),
IN `param_lat` DOUBLE,
IN `param_lng` DOUBLE,
IN `param_radius` INT
)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT 'Find markers based upon given starting lat/lng, a name and a radius'
begin
/* user supplied parameters / variables */
declare lat double default 0;
declare lng double default 0;
declare radius integer default 0;
declare _name varchar(64);
/* default parameters / variables */
declare earth_radius integer default 0;
declare lon1 float;
declare lat1 float;
declare lon2 float;
declare lat2 float;
/* create variables from input parameters */
set @lat=param_lat;
set @lng=param_lng;
set @radius=cast( param_radius as signed );
set @earth_radius=3956;
set @_name=cast(param_name as char character set utf8);
/* internal variables ~ do not edit */
set @lon1 = @lng - @radius/abs( cos( radians(@lat) ) * 69 );
set @lon2 = @lng + @radius/abs( cos( radians(@lat) ) * 69 );
set @lat1 = @lat - (@radius/69);
set @lat2 = @lat + (@radius/69);
select distinct
m.`id`,
m.`name`,
m.`address`,
m.`lat`,
m.`lng`,
round( @earth_radius * 2 * asin( sqrt( power( sin( ( @lat - m.`lat` ) * pi() / 180 / 2 ), 2 ) + cos( @lat * pi() / 180 ) * cos( m.`lat` * pi() / 180 ) *power( sin( ( @lng - m.`lng` ) * pi() / 180 / 2 ), 2 ) ) ),2) as `distance`
from `markers` m
where
( m.`lat` between @lat1 and @lat2 )
and
( m.`lng` between @lon1 and @lon2 )
/*
the `having` clause might not suit your requirements... edit as appropriate!
*/
having `distance` <= @radius or `name` like concat( '%', @_name, '%' ) or `address` like concat( '%', @_name, '%' )
order by `distance`
/*
A dynamic limit is possible but causes many issues with the PHP prepared statement
as you need to do a prepared statement within the stored procedure.
*/
limit 0,20;
end
Пример сгенерированного xml (в зависимости от базы данных)
<?xml version="1.0" encoding="utf-8"?>
<markers>
<marker id="230" name="Fletcherfield 2x45m turbines?" address="1 Dameye Farm Cottages, Logie, Kirriemuir, Angus DD8 5PR, UK" lat="56.660805" lng="-2.978641" distance="1.37"/>
<marker id="143" name="Longbank Kirriemuir - height not known" address="2 Crieff Terrace, Kirriemuir, Angus DD8 4LU, UK" lat="56.693573" lng="-2.961416" distance="2.04"/>
<marker id="229" name="Auchindorie 1x46m turbine" address="Auchindorie, Kirriemuir, Angus DD8, UK" lat="56.651802" lng="-3.039208" distance="2.10"/>
<marker id="97" name="Reedie Farm 2x45.6m turbines" address="A926, Kirriemuir, Angus DD8, UK" lat="56.658123" lng="-3.055901" distance="2.31"/>
<marker id="232" name="Cairnleith Farm 1x19.9m turbine" address="Ghenty, Kirriemuir, Angus DD8 5HF, UK" lat="56.669613" lng="-3.081222" distance="2.97"/>
<marker id="87" name="Genty, Airlie 1x34.5m turbine" address="Ghenty, Kirriemuir, Angus DD8, UK" lat="56.662685" lng="-3.084762" distance="3.20"/>
<marker id="66" name="Cortachy - Gallow Hill 1 x 46m turbine" address="B955, Kirriemuir, Angus DD8, UK" lat="56.731060" lng="-2.968154" distance="4.09"/>
<marker id="99" name="Sprottie Fauld 20m turbine" address="A94, Forfar, Angus DD8, UK" lat="56.610363" lng="-3.037190" distance="4.65"/>
<marker id="75" name="East Memus Farm 1x76.5m turbine" address="4 Church Terrace, Memus, Forfar, Angus DD8 3TY, UK" lat="56.731243" lng="-2.932820" distance="4.71"/>
<marker id="58" name="South Leckaway 1x78m turbine" address="3 Mains of Brigton Cottages, Forfar, Angus DD8 1TH, UK" lat="56.621323" lng="-2.923994" distance="4.79"/>
<marker id="145" name="Miltonbank Farm - height not known" address="B957, Forfar, Angus DD8, UK" lat="56.705330" lng="-2.886529" distance="4.90"/>
<marker id="48" name="The Carrach - 9 x 84m turbines" address="B951, Kirriemuir, Angus DD8, UK" lat="56.693844" lng="-3.131382" distance="5.02"/>
<marker id="144" name="East Memus Farm 1x76.5m turbine 0.8MW" address="1 Goynd Steading, Glenogil, Forfar, Angus DD8 3SW, UK" lat="56.734554" lng="-2.922835" distance="5.12"/>
<marker id="223" name="The Welton 2x47m turbines" address="B951, Kirriemuir, Angus DD8, UK" lat="56.694519" lng="-3.134095" distance="5.13"/>
<marker id="211" name="Kalulu House 1x45.8m turbine" address="1 Whig Street, Forfar, Angus DD8, UK" lat="56.711098" lng="-2.878921" distance="5.34"/>
<marker id="124" name="W Mains of Whitewell 2x46m turbines" address="1 Whig St, Forfar, Angus DD8, UK" lat="56.705421" lng="-2.866838" distance="5.59"/>
<marker id="50" name="Craignathro 1 x 24m turbine" address="Dundee Rd, Forfar, Angus DD8, UK" lat="56.624084" lng="-2.884898" distance="5.72"/>
<marker id="85" name="Ladenford 1x 46.5 turbine" address="1 Craignathro Farm Cottages, Forfar, Angus DD8 2LE, UK" lat="56.628838" lng="-2.877588" distance="5.75"/>
<marker id="128" name="Castleton of Eassie 3x25m turbines" address="A94, Forfar, Angus DD8, UK" lat="56.604778" lng="-3.086371" distance="5.79"/>
<marker id="53" name="Lour Farm 1x35m turbine" address="Dundee Rd, Forfar, Angus DD8, UK" lat="56.628407" lng="-2.875971" distance="5.82"/>
</markers>
PHP и HTML - для простоты объединены в одну страницу для тестирования.При работе с предоставленными пользователем данными в сочетании с доступом к базе данных в PHP рекомендуется использовать prepared statement
в mysqli или PDO, чтобы избежать атак с использованием SQL-инъекций.В приведенном ниже коде используется PDO
для уровня базы данных, но с таким же успехом это можно сделать с помощью mysqli
.
. Сам SQL в этом примере записывается как stored procedure
(как видно выше),Начальная область просмотра карты сосредоточена в Шотландии, а не в Австралии, так как моя база данных в основном содержит местоположения внутри и вокруг показанной области.Я нашел несколько незначительных ошибок в javascript, но, если повезет, исправил их здесь.
<?php
/*
/11403878/poluchenie-otsutstvuyschih-dannyh-iz-uchebnika-po-poisku-lokatora-bazy-dannyh-google-php
This PHP code emulates the code that would be used in `storelocator.php`
and is presented inline for demo/test purposes only
*/
error_reporting( E_ALL );
ini_set( 'display_errors', 1 );
if( $_SERVER['REQUEST_METHOD']=='GET' && !empty( $_GET['lat'] ) && !empty( $_GET['lng'] ) ){
ob_clean();
header( 'Content-type: text/plain' );
$xml='no results';
/* PDO Database connection */
/*
database connection could very easily be an external file
called using `require` or `include` etc
*/
$dbport = 3306;
$dbhost = 'localhost';
$dbuser = 'root';
$dbpwd = 'xxx';
$dbname = 'xxx';
$options=array(
PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL,
PDO::ATTR_PERSISTENT => false,
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
PDO::ATTR_EMULATE_PREPARES => true,
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'utf8mb4\' COLLATE \'utf8mb4_unicode_ci\', @@sql_mode = STRICT_ALL_TABLES, @@foreign_key_checks = 1'
);
/* generate db connection string and PDO connection object */
$dsn='mysql:host='.$dbhost.';port='.$dbport.';dbname='.$dbname.';charset=UTF8';
$db = new PDO( $dsn, $dbuser, $dbpwd, $options );
/* sql statement to call the stored procedure in a prepared statement */
$sql='call `spFindMarkers`( :name, :lat, :lng, :radius );';
/* create the prepared statement */
$stmt=$db->prepare( $sql );
if( $stmt ){
/* filter incoming GET variables */
$args=array(
'lat' => FILTER_SANITIZE_NUMBER_FLOAT,
'lng' => FILTER_SANITIZE_NUMBER_FLOAT,
'radius' => FILTER_SANITIZE_NUMBER_INT,
'limit' => FILTER_SANITIZE_NUMBER_INT,
'name' => FILTER_SANITIZE_STRING
);
filter_input_array( INPUT_GET, $args );
$_GET = array_filter( $_GET );
/* create variables from GET parameters */
extract( $_GET );
/* create the SQL bound parameters array */
$params=array(
':name' => $name,
':lat' => $lat,
':lng' => $lng,
':radius' => $radius
);
/* execute the prepared statement */
$res = $stmt->execute( $params );
if( $res ){
/* Generate the XML response */
$dom = new DOMDocument('1.0','utf-8');
$root = $dom->appendChild( $dom->createElement('markers') );
/* process the recordset */
while( $rs=$stmt->fetch( PDO::FETCH_OBJ ) ){
$marker = $dom->createElement('marker');
$marker->setAttribute( 'id', $rs->id );
$marker->setAttribute( 'name', urldecode( $rs->name ) );
$marker->setAttribute( 'address', urldecode( $rs->address ) );
$marker->setAttribute( 'lat', $rs->lat );
$marker->setAttribute( 'lng', $rs->lng );
$marker->setAttribute( 'distance', $rs->distance );
$root->appendChild( $marker );
}
$xml=$dom->saveXML();
header( 'Content-type: application/xml' );
}
} else {
$xml='error';
}
$dom = $root = $db = null;
exit( $xml );
}
?>
<!DOCTYPE html >
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<title>Creating a Store Locator on Google Maps</title>
<style>
#map {
height:calc(100vh - 2.6rem);
width:100%;
}
html, body {
height:100vh;
width:100%;
margin: 0;
padding: 0;
}
form{
height:2.5rem;
display:flex;
flex-direction:row;
flex-align:flex-start;
justify-content:flex-start;
align-items:center;
background:gold;
border-bottom:2px solid black;
font-family:calibri,verdana,arial;
}
form *{ padding:0.25rem;}
option[selected]{background:gold;}
</style>
<script>
var map;
var marker;
var circle;
var markers = [];
var infoWindow;
var oSelect;
function initMap() {
var uluru = {
lat: -25.344,
lng: 131.036
};
var default_location = {
lat:56.646577,
lng:-2.888609
};
map = new google.maps.Map( document.getElementById('map'), {
zoom: 10,
center: default_location,/* change to uluru ? */
mapTypeId:'roadmap',
mapTypeControlOptions:{ style: google.maps.MapTypeControlStyle.DROPDOWN_MENU }
});
/*
https://groups.google.com/forum/#!msg/Google-Maps-API/2k3T5hwI5Ck/RRso0D2jB1oJ
for icons....
*/
marker = new google.maps.Marker( { position: default_location, map: map, icon:'//maps.google.com/mapfiles/ms/icons/blue-pushpin.png' });
infoWindow = new google.maps.InfoWindow();
oRadius=document.getElementById('radiusSelect');
oBttn=document.getElementById('bttn');
oBttn.onclick = searchLocations;
oSelect=document.getElementById('locationSelect');
oSelect.onchange=function(event){
if( this.value != 'none' ){
oSelect.value=this.value;
google.maps.event.trigger( markers[ this.value ], 'click' );
}
};
circle=createcircle( default_location, oRadius.value );
oRadius.addEventListener( 'change',function(){
circle.setRadius( this.value * 1000 );
},false);
}
function searchLocations() {
var address = document.getElementById("addressInput").value;
var geocoder = new google.maps.Geocoder();
if( address=='' ){
alert('No point searching for a blank address now is there?');
return false;
}
geocoder.geocode({address: address}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
searchLocationsNear( results[0].geometry.location );
marker.setPosition( results[0].geometry.location );
circle.setCenter( results[0].geometry.location );
circle.setMap( map );
} else {
alert( address + ' not found' );
}
});
}
function downloadUrl(url,callback) {
var request = window.ActiveXObject ? new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest;
request.onreadystatechange = function() {
if( this.readyState == 4 && this.status==200 ) callback( this.responseText, this.status );
};
request.open('GET', url, true);
request.send(null);
}
const createcircle=function(position,radius){
circle = new google.maps.Circle({
center:position,
radius:parseFloat( radius ) * 1000,
fillColor:'red',
fillOpacity:0.05,
strokeColor:'red',
strokeWeight:1,
clickable:true,
geodesic:true,
map:map
});
google.maps.event.addListener( circle, 'click', function(e){
this.setMap( null )
}.bind( circle ) );
return circle;
}
function searchLocationsNear( center ) {
clearLocations();
var radius = document.getElementById('radiusSelect').value;
var name = document.getElementById('addressInput').value;
var searchUrl = 'storelocator.php?lat=' + center.lat() + '&lng=' + center.lng() + '&radius=' + radius+'&name='+name;
/*
*
*
*
*
*
*
*****************************************/
/* !!! FOR TESTING ONLY - SAME PAGE !!! */
searchUrl ='?lat='+center.lat()+'&lng='+center.lng()+'&radius='+radius+'&name='+name
/* !!! FOR TESTING ONLY - SAME PAGE !!! */
/*****************************************
*
*
*
*
*
*
*
*/
downloadUrl( searchUrl, function( data ) {
var xml = parseXML( data );
var nodes = xml.documentElement.getElementsByTagName('marker');
var bounds = new google.maps.LatLngBounds();
if( nodes.length == 0 ){
alert('no results')
return;
}
for( var i = 0; i < nodes.length; i++ ) {
var node=nodes[i];
var id = node.getAttribute('id');
var name = node.getAttribute('name');
var name = node.getAttribute('name');
var address = node.getAttribute('address');
var distance = parseFloat(node.getAttribute('distance'));
var latlng = new google.maps.LatLng(
parseFloat( node.getAttribute('lat') ),
parseFloat( node.getAttribute('lng') )
);
createOption( name, distance, i, id );
createMarker( latlng, name, address, id );
bounds.extend( latlng );
}
map.fitBounds( bounds );
});
}
function createMarker( latlng, name, address, rid ) {
var html = "<b>" + name + "</b> <br/>" + address;
var marker = new google.maps.Marker({
map: map,
position: latlng,
rid:rid,
icon:'//maps.google.com/mapfiles/ms/icons/red-pushpin.png'
});
google.maps.event.addListener( marker, 'click', function(event){
infoWindow.setContent( html );
infoWindow.open( map, this );
oSelect.value=oSelect.querySelector('option[data-rid="'+this.rid+'"]').value;
});
markers.push( marker );
}
function createOption( name, distance, num, rid ) {
var option = document.createElement('option');
option.value = num;
option.innerHTML = name;
option.dataset.rid = rid;
oSelect.appendChild( option );
}
function clearLocations() {
infoWindow.close();
for( var i = 0; i < markers.length; i++ ) {
markers[i].setMap(null);
}
markers.length = 0;
oSelect.innerHTML = '';
var option = document.createElement('option');
option.value = 'none';
option.innerHTML = 'See all results:';
oSelect.appendChild(option);
oSelect.style.visibility = 'visible';
}
const parseXML=function( xml ){
try{
if( window.ActiveXObject ){
let oParser=new ActiveXObject('Microsoft.XMLDOM');
oParser.loadXML( xml );
return oParser;
} else {
let oParser = new DOMParser();
return oParser.parseFromString(xml,'application/xml');
}
}catch( err ){
console.log( err );
}
}
</script>
<script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCbn9gIka8i-33HotlIor7GHMt2WEo_aAQ&callback=initMap"></script>
</head>
<body>
<form method='post'>
<label for='raddressInput'>Search location:</label>
<input type='text' id='addressInput' name='addressInput' size='15' value='kirrie' />
<label for='radiusSelect'>Radius:</label>
<select id='radiusSelect' name='radiusSelect' label='Radius'>
<option value=100>100 kms
<option value=90>90 kms
<option value=80>80 kms
<option value=70>70 kms
<option value=60>60 kms
<option value='50'>50 kms
<option value=40>40 kms
<option value='30'>30 kms
<option value='20'>20 kms
<option value='10' selected>10 kms
<option value='5'>5 kms
<option value='1'>1 kms
</select>
<input type='button' id='bttn' value='Search' />
<select id='locationSelect' name='locationSelect' style='width: 10%; visibility: hidden'></select>
</form>
<div id='map'></div>
</body>
</html>