I am currently using PHP to query from my sphinx index. The index is building properly, however the search is not.
Originally I had my query set up like the following:
private function _doMapSearchAll($textSearch, $typeFilterArr, $rads = array(), $centre = array(), $showFavourites = false) {
// lookup the location based on the search and return the results in that
// map bounds ignore for boundary searches
$rsArr = array();
$skipDataSearch = false;
if (!COUNT($rads)) {
// handle airport code lookups if we have 3 characters
if (strlen($textSearch) === 3) {
$rsArr = $this->_doMapAirportSearch($textSearch);
if (COUNT($rsArr)) {
$skipDataSearch = true;
}
}
// still no results based on the above search, do generic map search
if ($skipDataSearch === false) {
$rsArr = $this->_doMapGeoSearch($textSearch, $typeFilterArr, $centre);
if (COUNT($rsArr['results']) > 0) {
$skipDataSearch = false;
}
}
}
// if we are doing a boundary search, or we have no results from the above,
// get all results based on our location
if ($skipDataSearch === false) {
// fall back onto searching via the data
// normalise the search string
$originalSearchString = $this->doNormalisation($textSearch);
// use sphinx for the search
$sphinx = $this->_getSphinxConnection();
$sphinx->setLimits(0, $this->container->getParameter('search_max_results'), $this->container->getParameter('search_max_results'));
if (COUNT($typeFilterArr)) {
$sphinx->SetFilter('fldSiteTypeUID_attr', $typeFilterArr);
}
// if we're doing a boundary search, skip the search string
if (COUNT($rads) > 0) {
$originalSearchString = '';
if ($showFavourites === false) {
//$sphinx->SetFilterFloatRange('lat_radians', min($rads['minLat'], $rads['maxLat']), max($rads['minLat'], $rads['maxLat']));
//$sphinx->SetFilterFloatRange('long_radians', min($rads['minLon'], $rads['maxLon']), max($rads['minLon'], $rads['maxLon']));
if ($rads['minLat'] > $rads['maxLat']) {
$rads['minLat'] = $rads['minLat'] - 360;
}
if ($rads['minLon'] > $rads['maxLon']) {
$rads['minLon'] = $rads['minLon'] - 180;
}
$sphinx->SetFilterFloatRange('lat_radians', $rads['minLat'], $rads['maxLat']);
$sphinx->SetFilterFloatRange('long_radians', $rads['minLon'], $rads['maxLon']);
}
}
// order by centre point
if (COUNT($centre) > 0) {
// otherwise start in the centre
$sphinx->SetGeoAnchor('lat_radians', 'long_radians', (float) $centre['centreLat'], (float) $centre['centreLon']);
$lintDistanceLimit = 999999999.0; // everything
if ($showFavourites === false && isset($rads['maxLat'])) {
$radiusMiles = $this->get('geolocation_helper')->getSeparation(
rad2deg($rads['maxLat']),
rad2deg($rads['minLon']),
rad2deg($rads['minLat']),
rad2deg($rads['maxLon']),
"M"
);
$lintDistanceLimit = $radiusMiles * 1609; // miles to meters...
}
$sphinx->SetFilterFloatRange('geodist', 0.0, (float)$lintDistanceLimit);
$sphinx->SetSortMode(SPH_SORT_EXTENDED, 'geodist ASC');
} else {
// apply search weights
$sphinx->SetFieldWeights(array('fldTown_str' => 100, 'fldAddress1_str' => 30, 'fldSiteName_str' => 20));
}
// if we should be limiting to only favourites, pickup the selected
// favourites from cookies
if ($showFavourites === true) {
$request = $this->container->get('request_stack')->getCurrentRequest();
$favourites = explode(',', $request->cookies->get('HSF_favlist'));
if (count($favourites)) {
foreach ($favourites as $k => $favourite) {
$favourites[$k] = (int)$favourite;
}
$sphinx->SetFilter('fldUID_attr', $favourites);
}
}
$rs = $sphinx->Query($originalSearchString, $this->container->getParameter('sphinx_index'));
echo json_encode($sphinx);
$rsArr['results'] = array();
if (isset($rs['matches']) && count($rs['matches'])) {
// update the search text to what we actually searched for, this
// is needed in case we've updated/set $rsArr['searchText']
// after a geolookup
$rsArr['searchText'] = $textSearch;
// clear any previous bounds set by the geolookup, we don't want this
// if we have direct text match results
$rsArr['geobounds'] = array();
if (isset($rs['matches']) && count($rs['matches'])) {
foreach ($rs['matches'] as $k => $match) {
$rsArr['results'][$k] = $this->_remapSphinxData($k, $match);
}
}
// sort the results by distance
usort($rsArr['results'], function ($a, $b) {
return $a['fldDistance'] - $b['fldDistance'];
});
}
}
// add on the total record count
$rsArr['total'] = (int) COUNT($rsArr['results']);
return $rsArr;
}
That was returning nothing and giving me the error for GEODIST():
{"_host":"sphinx","_port":36307,"_path":"","_socket":false,"_offset":0,"_limit":250,"_mode":0,"_weights":[],"_sort":4,"_sortby":"geodist ASC","_min_id":0,"_max_id":0,"_filters":[{"type":2,"attr":"geodist","exclude":false,"min":0,"max":999999999}],"_groupby":"","_groupfunc":0,"_groupsort":"#group desc","_groupdistinct":"","_maxmatches":"250","_cutoff":0,"_retrycount":0,"_retrydelay":0,"_anchor":{"attrlat":"lat_radians","attrlong":"long_radians","lat":0.9300859583877783,"long":-2.0943951023931953},"_indexweights":[],"_ranker":0,"_rankexpr":"","_maxquerytime":0,"_fieldweights":[],"_overrides":[],"_select":"*","_error":"searchd error: geoanchor is deprecated (and slow); use GEODIST() expression","_warning":"","_connerror":false,"_reqs":[],"_mbenc":"","_arrayresult":false,"_timeout":0}{"results":[],"bounds":{"bllat":53.395603,"trlat":53.7159857,"bllng":-113.7138017,"trlng":-113.2716433},"geocentre":{"lat":53.5461245,"lon":-113.4938229},"checksum":"204a43923452936b00a10c8e566c4a48d4fdb280f97fd4042646eb45c8257bbc","searchText":"Edmonton, AB, Canada","skipResults":true,"showFavourites":false}
To fix this I changed the SetGeoAnchor function to the following:
private function _doMapSearchAll($textSearch, $typeFilterArr, $rads = array(), $centre = array(), $showFavourites = false) {
// lookup the location based on the search and return the results in that
// map bounds ignore for boundary searches
$rsArr = array();
$skipDataSearch = false;
if (!COUNT($rads)) {
// handle airport code lookups if we have 3 characters
if (strlen($textSearch) === 3) {
$rsArr = $this->_doMapAirportSearch($textSearch);
if (COUNT($rsArr)) {
$skipDataSearch = true;
}
}
// still no results based on the above search, do generic map search
if ($skipDataSearch === false) {
$rsArr = $this->_doMapGeoSearch($textSearch, $typeFilterArr, $centre);
if (COUNT($rsArr['results']) > 0) {
$skipDataSearch = false;
}
}
}
// if we are doing a boundary search, or we have no results from the above,
// get all results based on our location
if ($skipDataSearch === false) {
// fall back onto searching via the data
// normalise the search string
$originalSearchString = $this->doNormalisation($textSearch);
// use sphinx for the search
$sphinx = $this->_getSphinxConnection();
$sphinx->setLimits(0, $this->container->getParameter('search_max_results'), $this->container->getParameter('search_max_results'));
if (COUNT($typeFilterArr)) {
$sphinx->SetFilter('fldSiteTypeUID_attr', $typeFilterArr);
}
// if we're doing a boundary search, skip the search string
if (COUNT($rads) > 0) {
$originalSearchString = '';
if ($showFavourites === false) {
//$sphinx->SetFilterFloatRange('lat_radians', min($rads['minLat'], $rads['maxLat']), max($rads['minLat'], $rads['maxLat']));
//$sphinx->SetFilterFloatRange('long_radians', min($rads['minLon'], $rads['maxLon']), max($rads['minLon'], $rads['maxLon']));
if ($rads['minLat'] > $rads['maxLat']) {
$rads['minLat'] = $rads['minLat'] - 360;
}
if ($rads['minLon'] > $rads['maxLon']) {
$rads['minLon'] = $rads['minLon'] - 180;
}
$sphinx->SetFilterFloatRange('lat_radians', $rads['minLat'], $rads['maxLat']);
$sphinx->SetFilterFloatRange('long_radians', $rads['minLon'], $rads['maxLon']);
}
}
// order by centre point
if (COUNT($centre) > 0) {
// otherwise start in the centre
// $sphinx->SetGeoAnchor('lat_radians', 'long_radians', (float) $centre['centreLat'], (float) $centre['centreLon']);
$centreLat = (float) $centre['centreLat'];
$centreLon = (float) $centre['centreLon'];
$sphinx->SetSelect("GEODIST(lat_radians, long_radians, $centreLat, $centreLon) AS geodist");
$lintDistanceLimit = 999999999.0; // everything
if ($showFavourites === false && isset($rads['maxLat'])) {
$radiusMiles = $this->get('geolocation_helper')->getSeparation(
rad2deg($rads['maxLat']),
rad2deg($rads['minLon']),
rad2deg($rads['minLat']),
rad2deg($rads['maxLon']),
"M"
);
$lintDistanceLimit = $radiusMiles * 1609; // miles to meters...
}
$sphinx->SetFilterFloatRange('geodist', 0.0, (float)$lintDistanceLimit);
$sphinx->SetSortMode(SPH_SORT_EXTENDED, 'geodist ASC');
} else {
// apply search weights
$sphinx->SetFieldWeights(array('fldTown_str' => 100, 'fldAddress1_str' => 30, 'fldSiteName_str' => 20));
}
// if we should be limiting to only favourites, pickup the selected
// favourites from cookies
if ($showFavourites === true) {
$request = $this->container->get('request_stack')->getCurrentRequest();
$favourites = explode(',', $request->cookies->get('HSF_favlist'));
if (count($favourites)) {
foreach ($favourites as $k => $favourite) {
$favourites[$k] = (int)$favourite;
}
$sphinx->SetFilter('fldUID_attr', $favourites);
}
}
$rs = $sphinx->Query($originalSearchString, $this->container->getParameter('sphinx_index'));
echo json_encode($sphinx);
$rsArr['results'] = array();
if (isset($rs['matches']) && count($rs['matches'])) {
// update the search text to what we actually searched for, this
// is needed in case we've updated/set $rsArr['searchText']
// after a geolookup
$rsArr['searchText'] = $textSearch;
// clear any previous bounds set by the geolookup, we don't want this
// if we have direct text match results
$rsArr['geobounds'] = array();
if (isset($rs['matches']) && count($rs['matches'])) {
foreach ($rs['matches'] as $k => $match) {
$rsArr['results'][$k] = $this->_remapSphinxData($k, $match);
}
}
// sort the results by distance
usort($rsArr['results'], function ($a, $b) {
return $a['fldDistance'] - $b['fldDistance'];
});
}
}
// add on the total record count
$rsArr['total'] = (int) COUNT($rsArr['results']);
return $rsArr;
}
This gives me back results however they look like this:
{"_host":"sphinx","_port":36307,"_path":"","_socket":false,"_offset":0,"_limit":250,"_mode":0,"_weights":[],"_sort":4,"_sortby":"geodist ASC","_min_id":0,"_max_id":0,"_filters":[{"type":2,"attr":"geodist","exclude":false,"min":0,"max":999999999}],"_groupby":"","_groupfunc":0,"_groupsort":"#group desc","_groupdistinct":"","_maxmatches":"250","_cutoff":0,"_retrycount":0,"_retrydelay":0,"_anchor":[],"_indexweights":[],"_ranker":0,"_rankexpr":"","_maxquerytime":0,"_fieldweights":[],"_overrides":[],"_select":"GEODIST(lat_radians, long_radians, 0.93008595838778, -2.0943951023932) AS geodist","_error":"","_warning":"","_connerror":false,"_reqs":[],"_mbenc":"","_arrayresult":false,"_timeout":0}{"results":[[null,null,null,null,null,null,null,"Other","http:\/\/localhost:8080\/themes\/docker\/images\/Site-Type-Other.png",null,34362776,null,""],[null,null,null,null,null,null,null,"Other","http:\/\/localhost:8080\/themes\/docker\/images\/Site-Type-Other.png",null,38279990,null,""],[null,null,null,null,null,null,null,"Other","http:\/\/localhost:8080\/themes\/docker\/images\/Site-Type-Other.png",null,7963188,null,""],[null,null,null,null,null,null,null,"Other","http:\/\/localhost:8080\/themes\/docker\/images\/Site-Type-Other.png",null,7971966,null,""],[null,null,null,null,null,null,null,"Other","http:\/\/localhost:8080\/themes\/docker\/images\/Site-Type-Other.png",null,31790051,null,""],[null,null,null,null,null,null,null,"Other","http:\/\/localhost:8080\/themes\/docker\/images\/Site-Type-Other.png",null,7972301,null,""],[null,null,null,null,null,null,null,"Other","http:\/\/localhost:8080\/themes\/docker\/images\/Site-Type-Other.png",null,33589292,null,""],[null,null,null,null,null,null,null,"Other","http:\/\/localhost:8080\/themes\/docker\/images\/Site-Type-Other.png",null,33589642,null,""],[null,null,null,null,null,null,null,"Other","http:\/\/localhost:8080\/themes\/docker\/images\/Site-Type-Other.png",null,7962913,null,""],[null,null,null,null,null,null,null,"Other","http:\/\/localhost:8080\/themes\/docker\/images\/Site-Type-Other.png",null,31789941,null,""],[null,null,null,null,null,null,null,"Other","http:\/\/localhost:8080\/themes\/docker\/images\/Site-Type-Other.png",null,7962178,null,""],[null,null,null,null,null,null,null,"Other","http:\/\/localhost:8080\/themes\/docker\/images\/Site-Type-Other.png",null,49484181,null,""],[null,null,null,null,null,null,null,"Other","http:\/\/localhost:8080\/themes\/docker\/images\/Site-Type-Other.png",null,31795436,null,""],
.....
My searchd config looks like this:
searchd
{
listen = 36307
listen = 9306:mysql41
log = /opt/sphinx/searchd.log
query_log = /opt/sphinx/query.log
read_timeout = 5
max_children = 30
pid_file = /opt/sphinx/searchd.pid
seamless_rotate = 1
preopen_indexes = 1
unlink_old = 1
binlog_path = /opt/sphinx/
}
I am new tp Sphinx and need help correcting this query. Any thoughts?
Edit: This is sphinx version 3.4.1
So after hours on this over the past few days, I have found the answer...:
I was missing the * in the statement:
$sphinx->SetSelect("*, GEODIST($centreLat, $centreLon, lat_radians, long_radians) as geodist");
I hope this helps anyone who has this issue in the future
I have a internal site which uses php to look through my msql customer database. Find any customers which do not have lat and lng fields filled in. Grab the postcodes and geocode them posting the lat and lng back to my database and plot the customers on the map. This is done by a cron job once a day. This worked fine using v.2 of google api. Since march or april its stopped. Im guessing because of v.3.
Jist my jl_jobscoordinates.cron.php file searches through the database picking up all the postcodes for empty lat and lng fields. Then calls a function from my geocode.class.php called doGeocode which uses xml to put togther and find results and save the lat and lng. Inside the geocodeclass it refers to a m_url which is the googleapi url which is saved inside my config file. I have updated this url to the new v.3 url which is http://maps.googleapis.com/maps/api/geocode/xml?address=%s&sensor=false. My map is back up and running, just nothing will geocode.
I will paste the two files jl_jobscooedinates.cron.php and geocode.class.php. I have commented out the old xml in the geocode which used to work with the old url.
The results of my cron is that it is not getting coordinates. e.g. -- [3-2013] Google could not find this Postcode: [COO041] Test Company Name, Oxfordshire OX26 4SS
jl_jobcoordinates.cron.php
require_once("../includes/config.php");
require_once(_PATH_JMS."/classes/session.class.php");
require_once(_PATH_JMS."/classes/db.class.php");
require_once(_PATH_JMS."/classes/lib.class.php");
require_once(_PATH_JMS."/classes/security.class.php");
require_once(_PATH_JMS."/classes/emails.class.php");
require_once(_PATH_JMS."/classes/geocode.class.php");
require_once(_PATH_JMS."/services/actiontrail.ds.php");
require_once(_PATH_JMS."/services/jobsdue.ds.php");
//-----------------------------------------------------
// Main Object Instances - Initialize what we require
//-----------------------------------------------------
$DB = new DB();
$Security = new Security($DB->i_db_conn);
$Lib = new Lib();
$Session = new Session();
$ActionTrail = new ActionTrail($DB, $Session, $Security);
$JobsDue = new JobsDue($DB, $Session, $Security, $ActionTrail);
$Geocode = new Geocode($Session, $Security);
$Emails = new Emails($DB, $Session, $Security);
//-----------------------------------------------------
// Save as a valid system user
//-----------------------------------------------------
$Session->save('USR_AUTH',_CRON_USER_NAME);
$Session->save('USR_PASS',_CRON_USER_PASS);
$Session->save('USR_IS_EMPLOYED', '1');
$Session->save('CONS',$Session->get('USR_AUTH'));
//-----------------------------------------------------
// Postcodes to Ignore - we cannot geocode these
//-----------------------------------------------------
$m_ignore = array("IRL","IRELAND","IRE","ITA","USA","BEL","EGY","GER","FR","FRA","HOL","POL");
//-----------------------------------------------------
// Get Jobs Due for all consultants for this year and next
//-----------------------------------------------------
$mY = (int) date("Y");
//-----------------------------------------------------
// Find t-cards without lat & lng
//-----------------------------------------------------
$m_errors = array();
for ($y=$mY;$y<=$mY+1;$y++)
{
for ($i=1;$i<=12;$i++)
{
$mM = (int) $i;
//echo "<br> mM =".$mM ." i =".$i;
$mJobs = $JobsDue->getAllJobsDue('%',$mM,$y,'%',NULL,NULL,FALSE); /* DON'T GET MISSED JOBS AS WE WILL START FROM JAN */
//echo "<br>mJobs =".$mJobs;
foreach ($mJobs as $row)
{
$m_postcode = $Lib->lib_str_clean(trim($row->postcode)); //this loops through each of the records and gets the post codes. m_postcodes are the postcodes found
echo "<br>m_postcode =".$m_postcode;
if (($row->latlngexists == 1)||(in_array($m_postcode,$m_ignore))||(in_array($row->card_id,$m_ignore))||(strlen($m_postcode)<=0)) continue;
if ($Lib->lib_ispostcode($m_postcode)) {
$m_coordinates = $Geocode->doGeocode($m_postcode);
echo "<br>m_coords =".$m_coordinates;//nothing displayed
if ($m_coordinates != NULL) {
$DB->setGeoTCard($row->card_id,$m_coordinates['lat'],$m_coordinates['lng']);
} else {
$m_err_desc = sprintf("[%s-%s] Google could not find this Postcode",$mM,$y);
$m_error = array(
"err_desc" => $m_err_desc,
"err_code" => $row->client_code,
"err_comp" => $row->title,
"err_depo" => $row->description,
"err_post" => $m_postcode
);
$m_errors[] = $m_error;
$m_ignore[] = $row->card_id;
}
sleep(_GEOCODE_PAUSE);
} else {
$m_err_desc = sprintf("[%s-%s] Postcode is invalid please check",$mM,$y);
$m_error = array(
"err_desc" => $m_err_desc,
"err_code" => $row->client_code,
"err_comp" => $row->title,
"err_depo" => $row->description,
"err_post" => $m_postcode
);
$m_errors[] = $m_error;
$m_ignore[] = $row->card_id;
}
}
}
}
if (count($m_errors) > 0) {
$Emails->doGeocodeErrNotify($m_errors);
}
geocode.class.php
class Geocode {
private $m_session = NULL;
private $m_security = NULL;
private $m_session_user;
private $m_session_pass;
private $m_key = _GMAP_KEY;
private $m_url = _GMAP_URL;
private $m_res = Array();
public function __construct($p_session,$p_security)
{
$this->m_session = $p_session;
$this->m_security = $p_security;
$this->m_session_user = $this->m_session->get('USR_AUTH');
$this->m_session_pass = $this->m_session->get('USR_PASS');
if ($this->m_security->doLogin($this->m_session_user,$this->m_session_pass) <= 0)
{
return NULL;
die;
}
}
public function doGeocode($p_postcode)
{
try {
// //$xml = new SimpleXMLElement(sprintf($this->m_url,$p_postcode,$this->m_key),0,TRUE); //OLD FOR V.2
$xml = new SimpleXMLElement(sprintf($this->m_url,$p_postcode),0,TRUE);
} catch (Exception $e) {
echo sprintf('Caught exception: %s', $e->getMessage());
return NULL;
die;
}
$st = $xml->Response->Status->code;
if (strcmp($st, "200") == 0)
{
$co = $xml->Response->Placemark->Point->coordinates;
$cs = preg_split("/[\s]*[,][\s]*/", $co);
$this->m_res = Array(
"lng" => $cs[0],
"lat" => $cs[1],
"alt" => $cs[2]
);
return $this->m_res;
} else {
return NULL;
}
}
}
I would really appriciate if someone could help me please. Im guessing its something to do with the new url in my config file and the current xml not set properly for the sensor??
My geocode stuff is still working fine just like this don't forget to use your own personal API key!
/**
* Geocode postcode to get long/lat used when adding suppliers and sites
* #param - $postcode - string - Input post code to geocode
* #return - $lat,$long - array - array containing latitude coords
*/
function geocode($postcode) {
$postcode = urlencode(trim($postcode)); // post code to look up in this case status however can easily be retrieved from a database or a form post
//$request_url = "http://maps.googleapis.com/maps/api/geocode/xml?address=".$postcode."&sensor=false"; // the request URL you'll send to google to get back your XML feed
define("MAPS_HOST", "maps.google.co.uk");
define("KEY", "YOUR API KEY HERE");
$base_url = "http://" . MAPS_HOST . "/maps/geo?output=xml" . "&key=" . KEY;
$request_url = $base_url . "&q=" . $postcode;
$xml = simplexml_load_file($request_url);
$status = $xml->Response->Status->code;
if (strcmp($status, "200") == 0) {
// Successful geocode
$geocode_pending = false;
$coordinates = $xml->Response->Placemark->Point->coordinates;
$coordinatesSplit = explode(",", $coordinates);
// Format: Longitude, Latitude, Altitude
return array("lat"=>$coordinatesSplit[1],"long"=>$coordinatesSplit[0]);
} else {
return array("lat"=>0,"long"=>0);
}
}
I have a latitude and a longitude, and I need to fetch country.
I am using this:
$geocode_stats = file_get_contents("http://maps.googleapis.com/maps/api/geocode/json?latlng=".$deal_lat.",".$deal_long . "&sensor=false");
$output_deals = json_decode($geocode_stats);
$country = $output_deals->results[2]->address_components[4]->long_name;
Sometimes it gives a correct country name, but sometimes it gives blank values, and sometimes it returns a city name.
Can anybody help?
I wrote a function to make it easy. Simply pass in your geo-address which can be a full address, zip code, or in your case latitude and longitude. It will then search through the address component array for the country. In the case that it is unable to find the county it will simply return null, you can change that to an empty string ("") if you need to.
function getGeoCounty($geoAddress) {
$url = 'http://maps.google.com/maps/api/geocode/json?address=' . $geoAddress .'&sensor=false';
$get = file_get_contents($url);
$geoData = json_decode($get);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \InvalidArgumentException('Invalid geocoding results');
}
if(isset($geoData->results[0])) {
foreach($geoData->results[0]->address_components as $addressComponent) {
if(in_array('administrative_area_level_2', $addressComponent->types)) {
return $addressComponent->long_name;
}
}
}
return null;
}
Get complete Address from latitude and longitude.
$url = 'http://maps.googleapis.com/maps/api/geocode/json?latlng='.trim($lat).','.trim($lng).'&sensor=false';
$json = #file_get_contents($url);$data=json_decode($json);
echo $data->results[0]->formatted_address;
$deal_lat=30.469301;
$deal_long=70.969324;
$geocode=file_get_contents('http://maps.googleapis.com/maps/api/geocode/json?latlng='.$deal_lat.','.$deal_long.'&sensor=false');
$output= json_decode($geocode);
for($j=0;$j<count($output->results[0]->address_components);$j++){
$cn=array($output->results[0]->address_components[$j]->types[0]);
if(in_array("country", $cn)){
$country= $output->results[0]->address_components[$j]->long_name;
}
}
echo $country;
$deal_lat=30.469301;
$deal_long=70.969324;
$geocode=file_get_contents('http://maps.googleapis.com/maps/api/geocode/json?latlng='.$deal_lat.','.$deal_long.'&sensor=false');
$output= json_decode($geocode);
for($j=0;$j<count($output->results[0]->address_components);$j++){
$cn=array($output->results[0]->address_components[$j]->types[0]);
if(in_array("country", $cn)){
$country= $output->results[0]->address_components[$j]->long_name;
}
}
echo $country;
I did take the code of matwonk as base.
function getGeoCounty($geoAddress) {
$url = 'http://maps.google.com/maps/api/geocode/json?address=' . urlencode($geoAddress) .'&sensor=false';
$get = file_get_contents($url);
$geoData = json_decode($get);
if(isset($geoData->results[0])) {
$return = array();
foreach($geoData->results[0]->address_components as $addressComponet) {
if(in_array('political', $addressComponet->types)) {
if($addressComponet->short_name != $addressComponet->long_name)
$return[] = $addressComponet->short_name. " - " . $addressComponet->long_name;
else
$return[] = $addressComponet->long_name;
}
}
return implode(", ",$return);
}
return null;
}
The code return in format: neighborhood, City, State, Country
if detect a shortname (as country code or state code) appear in format CODE - NAME.
$latlng = '22.7314,75.88243';
$request_url = "http://maps.googleapis.com/maps/api/geocode /xml?latlng=".$latlng."&sensor=true";
$xml = simplexml_load_file($request_url);
if($xml->status == "OK") {
$address = $xml->result->formatted_address;
foreach ($xml->result->address_component as $address) {
if("country" == trim($address->type)) {
$country = $address->short_name;
}
}
}
I'm really stumped on how Twitter expects users of its API to convert the plaintext tweets it sends to properly linked HTML.
Here's the deal: Twitter's JSON API sends this set of information back when you request the detailed data for a tweet:
{
"created_at":"Wed Jul 18 01:03:31 +0000 2012",
"id":225395341250412544,
"id_str":"225395341250412544",
"text":"This is a test tweet. #boring #nbc http://t.co/LUfDreY6 #skronk #crux http://t.co/VpuMlaDs #twitter",
"source":"web",
"truncated":false,
"in_reply_to_status_id":null,
"in_reply_to_status_id_str":null,
"in_reply_to_user_id":null,
"in_reply_to_user_id_str":null,
"in_reply_to_screen_name":null,
"user": <REDACTED>,
"geo":null,
"coordinates":null,
"place":null,
"contributors":null,
"retweet_count":0,
"entities":{
"hashtags":[
{
"text":"boring",
"indices":[22,29]
},
{
"text":"skronk",
"indices":[56,63]
}
],
"urls":[
{
"url":"http://t.co/LUfDreY6",
"expanded_url":"http://www.twitter.com",
"display_url":"twitter.com",
"indices":[35,55]
},
{
"url":"http://t.co/VpuMlaDs",
"expanded_url":"http://www.example.com",
"display_url":"example.com",
"indices":[70,90]
}
],
"user_mentions":[
{
"screen_name":"nbc",
"name":"NBC",
"id":26585095,
"id_str":"26585095",
"indices":[30,34]
},
{
"screen_name":"crux",
"name":"Z. D. Smith",
"id":407213,
"id_str":"407213",
"indices":[64,69]
},
{
"screen_name":"twitter",
"name":"Twitter",
"id":783214,
"id_str":"783214",
"indices":[91,99]
}
]
},
"favorited":false,
"retweeted":false,
"possibly_sensitive":false
}
The interesting parts, for this question, are the text element and the entries in the hashtags, user_mentions, and urls arrays. Twitter is telling us where in the text element the hastags, mentions, and urls appear with the indices arrays... so here's the crux of the question:
How do you use those indices arrays?
You can't just use them straight up by looping over each link element with something like substr_replace, since replacing the first link element in the text will invalidate all the index values for subsequent link elements. You also can't use substr_replace's array functionality, since it only works when you give it an array of strings for the first arg, rather than a single string (I've tested this. The results are... strange).
Is there some function that can simultaneously replace multiple index-delimited substrings in a single string with different replacement strings?
All you have to do to use the indices twitter provides straight up with a simple replace is collect the replacements you want to make and then sort them backwards. You can probably find a more clever way to build $entities, I wanted them optional anyway, so I KISS as far as that went.
Either way, my point here was just to show that you don't need to explode the string and character count and whatnot. Regardless of how you do it, all you need to to is start at the end and work to the beginning of the string, and the index twitter has is still valid.
<?php
function json_tweet_text_to_HTML($tweet, $links=true, $users=true, $hashtags=true)
{
$return = $tweet->text;
$entities = array();
if($links && is_array($tweet->entities->urls))
{
foreach($tweet->entities->urls as $e)
{
$temp["start"] = $e->indices[0];
$temp["end"] = $e->indices[1];
$temp["replacement"] = "<a href='".$e->expanded_url."' target='_blank'>".$e->display_url."</a>";
$entities[] = $temp;
}
}
if($users && is_array($tweet->entities->user_mentions))
{
foreach($tweet->entities->user_mentions as $e)
{
$temp["start"] = $e->indices[0];
$temp["end"] = $e->indices[1];
$temp["replacement"] = "<a href='https://twitter.com/".$e->screen_name."' target='_blank'>#".$e->screen_name."</a>";
$entities[] = $temp;
}
}
if($hashtags && is_array($tweet->entities->hashtags))
{
foreach($tweet->entities->hashtags as $e)
{
$temp["start"] = $e->indices[0];
$temp["end"] = $e->indices[1];
$temp["replacement"] = "<a href='https://twitter.com/hashtag/".$e->text."?src=hash' target='_blank'>#".$e->text."</a>";
$entities[] = $temp;
}
}
usort($entities, function($a,$b){return($b["start"]-$a["start"]);});
foreach($entities as $item)
{
$return = substr_replace($return, $item["replacement"], $item["start"], $item["end"] - $item["start"]);
}
return($return);
}
?>
Ok so I needed to do exactly this and I solved it. Here is the function I wrote. https://gist.github.com/3337428
function parse_message( &$tweet ) {
if ( !empty($tweet['entities']) ) {
$replace_index = array();
$append = array();
$text = $tweet['text'];
foreach ($tweet['entities'] as $area => $items) {
$prefix = false;
$display = false;
switch ( $area ) {
case 'hashtags':
$find = 'text';
$prefix = '#';
$url = 'https://twitter.com/search/?src=hash&q=%23';
break;
case 'user_mentions':
$find = 'screen_name';
$prefix = '#';
$url = 'https://twitter.com/';
break;
case 'media':
$display = 'media_url_https';
$href = 'media_url_https';
$size = 'small';
break;
case 'urls':
$find = 'url';
$display = 'display_url';
$url = "expanded_url";
break;
default: break;
}
foreach ($items as $item) {
if ( $area == 'media' ) {
// We can display images at the end of the tweet but sizing needs to added all the way to the top.
// $append[$item->$display] = "<img src=\"{$item->$href}:$size\" />";
}else{
$msg = $display ? $prefix.$item->$display : $prefix.$item->$find;
$replace = $prefix.$item->$find;
$href = isset($item->$url) ? $item->$url : $url;
if (!(strpos($href, 'http') === 0)) $href = "http://".$href;
if ( $prefix ) $href .= $item->$find;
$with = "$msg";
$replace_index[$replace] = $with;
}
}
}
foreach ($replace_index as $replace => $with) $tweet['text'] = str_replace($replace,$with,$tweet['text']);
foreach ($append as $add) $tweet['text'] .= $add;
}
}
It's an edge case but the use of str_replace() in Styledev's answer could cause issues if one entity is contained within another. For example, "I'm a genius! #me #mensa" could become "I'm a genius! #me #mensa" if the shorter entity is substituted first.
This solution avoids that problem:
<?php
/**
* Hyperlinks hashtags, twitter names, and urls within the text of a tweet
*
* #param object $apiResponseTweetObject A json_decoded() one of these: https://dev.twitter.com/docs/platform-objects/tweets
* #return string The tweet's text with hyperlinks added
*/
function linkEntitiesWithinText($apiResponseTweetObject) {
// Convert tweet text to array of one-character strings
// $characters = str_split($apiResponseTweetObject->text);
$characters = preg_split('//u', $apiResponseTweetObject->text, null, PREG_SPLIT_NO_EMPTY);
// Insert starting and closing link tags at indices...
// ... for #user_mentions
foreach ($apiResponseTweetObject->entities->user_mentions as $entity) {
$link = "https://twitter.com/" . $entity->screen_name;
$characters[$entity->indices[0]] = "<a href=\"$link\">" . $characters[$entity->indices[0]];
$characters[$entity->indices[1] - 1] .= "</a>";
}
// ... for #hashtags
foreach ($apiResponseTweetObject->entities->hashtags as $entity) {
$link = "https://twitter.com/search?q=%23" . $entity->text;
$characters[$entity->indices[0]] = "<a href=\"$link\">" . $characters[$entity->indices[0]];
$characters[$entity->indices[1] - 1] .= "</a>";
}
// ... for http://urls
foreach ($apiResponseTweetObject->entities->urls as $entity) {
$link = $entity->expanded_url;
$characters[$entity->indices[0]] = "<a href=\"$link\">" . $characters[$entity->indices[0]];
$characters[$entity->indices[1] - 1] .= "</a>";
}
// ... for media
foreach ($apiResponseTweetObject->entities->media as $entity) {
$link = $entity->expanded_url;
$characters[$entity->indices[0]] = "<a href=\"$link\">" . $characters[$entity->indices[0]];
$characters[$entity->indices[1] - 1] .= "</a>";
}
// Convert array back to string
return implode('', $characters);
}
?>
Jeff's solution worked well with English text but it got broken when the tweet contained non-ASCII characters. This solution avoids that problem:
mb_internal_encoding("UTF-8");
// Return hyperlinked tweet text from json_decoded status object:
function MakeStatusLinks($status)
{$TextLength=mb_strlen($status['text']); // Number of UTF-8 characters in plain tweet.
for ($i=0;$i<$TextLength;$i++)
{$ch=mb_substr($status['text'],$i,1); if ($ch<>"\n") $ChAr[]=$ch; else $ChAr[]="\n<br/>"; // Keep new lines in HTML tweet.
}
if (isset($status['entities']['user_mentions']))
foreach ($status['entities']['user_mentions'] as $entity)
{$ChAr[$entity['indices'][0]] = "<a href='https://twitter.com/".$entity['screen_name']."'>".$ChAr[$entity['indices'][0]];
$ChAr[$entity['indices'][1]-1].="</a>";
}
if (isset($status['entities']['hashtags']))
foreach ($status['entities']['hashtags'] as $entity)
{$ChAr[$entity['indices'][0]] = "<a href='https://twitter.com/search?q=%23".$entity['text']."'>".$ChAr[$entity['indices'][0]];
$ChAr[$entity['indices'][1]-1] .= "</a>";
}
if (isset($status['entities']['urls']))
foreach ($status['entities']['urls'] as $entity)
{$ChAr[$entity['indices'][0]] = "<a href='".$entity['expanded_url']."'>".$entity['display_url']."</a>";
for ($i=$entity['indices'][0]+1;$i<$entity['indices'][1];$i++) $ChAr[$i]='';
}
if (isset($status['entities']['media']))
foreach ($status['entities']['media'] as $entity)
{$ChAr[$entity['indices'][0]] = "<a href='".$entity['expanded_url']."'>".$entity['display_url']."</a>";
for ($i=$entity['indices'][0]+1;$i<$entity['indices'][1];$i++) $ChAr[$i]='';
}
return implode('', $ChAr); // HTML tweet.
}
Here is an updated answer that works with Twitter's new Extended Mode. It combines the answer by #vita10gy and the comment by #Hugo (to make it utf8 compatible), with a few minor tweaks to work with the new api values.
function utf8_substr_replace($original, $replacement, $position, $length) {
$startString = mb_substr($original, 0, $position, "UTF-8");
$endString = mb_substr($original, $position + $length, mb_strlen($original), "UTF-8");
$out = $startString . $replacement . $endString;
return $out;
}
function json_tweet_text_to_HTML($tweet, $links=true, $users=true, $hashtags=true) {
// Media urls can show up on the end of the full_text tweet, but twitter doesn't index that url.
// The display_text_range indexes show the actual tweet text length.
// Cut the string off at the end to get rid of this unindexed url.
$return = mb_substr($tweet->full_text, $tweet->display_text_range[0],$tweet->display_text_range[1]);
$entities = array();
if($links && is_array($tweet->entities->urls))
{
foreach($tweet->entities->urls as $e)
{
$temp["start"] = $e->indices[0];
$temp["end"] = $e->indices[1];
$temp["replacement"] = " <a href='".$e->expanded_url."' target='_blank'>".$e->display_url."</a>";
$entities[] = $temp;
}
}
if($users && is_array($tweet->entities->user_mentions))
{
foreach($tweet->entities->user_mentions as $e)
{
$temp["start"] = $e->indices[0];
$temp["end"] = $e->indices[1];
$temp["replacement"] = " <a href='https://twitter.com/".$e->screen_name."' target='_blank'>#".$e->screen_name."</a>";
$entities[] = $temp;
}
}
if($hashtags && is_array($tweet->entities->hashtags))
{
foreach($tweet->entities->hashtags as $e)
{
$temp["start"] = $e->indices[0];
$temp["end"] = $e->indices[1];
$temp["replacement"] = " <a href='https://twitter.com/hashtag/".$e->text."?src=hash' target='_blank'>#".$e->text."</a>";
$entities[] = $temp;
}
}
usort($entities, function($a,$b){return($b["start"]-$a["start"]);});
foreach($entities as $item)
{
$return = utf8_substr_replace($return, $item["replacement"], $item["start"], $item["end"] - $item["start"]);
}
return($return);
}
Here is a JavaScript version (using jQuery) of vita10gy's solution
function tweetTextToHtml(tweet, links, users, hashtags) {
if (typeof(links)==='undefined') { links = true; }
if (typeof(users)==='undefined') { users = true; }
if (typeof(hashtags)==='undefined') { hashtags = true; }
var returnStr = tweet.text;
var entitiesArray = [];
if(links && tweet.entities.urls.length > 0) {
jQuery.each(tweet.entities.urls, function() {
var temp1 = {};
temp1.start = this.indices[0];
temp1.end = this.indices[1];
temp1.replacement = '' + this.display_url + '';
entitiesArray.push(temp1);
});
}
if(users && tweet.entities.user_mentions.length > 0) {
jQuery.each(tweet.entities.user_mentions, function() {
var temp2 = {};
temp2.start = this.indices[0];
temp2.end = this.indices[1];
temp2.replacement = '#' + this.screen_name + '';
entitiesArray.push(temp2);
});
}
if(hashtags && tweet.entities.hashtags.length > 0) {
jQuery.each(tweet.entities.hashtags, function() {
var temp3 = {};
temp3.start = this.indices[0];
temp3.end = this.indices[1];
temp3.replacement = '#' + this.text + '';
entitiesArray.push(temp3);
});
}
entitiesArray.sort(function(a, b) {return b.start - a.start;});
jQuery.each(entitiesArray, function() {
returnStr = substrReplace(returnStr, this.replacement, this.start, this.end - this.start);
});
return returnStr;
}
You can then use this function like so ...
for(var i in tweetsJsonObj) {
var tweet = tweetsJsonObj[i];
var htmlTweetText = tweetTextToHtml(tweet);
// Do something with the formatted tweet here ...
}
Regarding vita10gy's helpful json_tweet_text_to_HTML(), I found a tweet that it could not format correctly: 626125868247552000.
This tweet has a nonbreaking space in it. My solution was to replace the first line of the function with the following:
$return = str_replace("\xC2\xA0", ' ', $tweet->text);
Performing a str_replace() on is covered here.