I'm currently working on a campaign/advertisement script in Laravel 5.2. I'm having a table with ads, for example: Ad name, Location (lat/long), Radius (+10km).
Now I have a user location (lat/long). I want to see if he is in the radius of any ad and show the ad to him.
When I search on Google I only find solution to search ads based on lat/long + radius but I want to opposite. So see if a lat/long is in a radius of existing ads.
What is the best way to make this? And advice would be appreciated
This should work, all you would need to do is add this function to your User model.
public function ads()
{
return \App\Ad::
crossJoin('users', 'users.id', '=', \DB::raw($this->attributes['id']))
->where(\DB::raw('69 * DEGREES(ACOS(COS(RADIANS(users.Latitude)) * COS(RADIANS(ads.Latitude)) * COS(RADIANS(users.Longitude - ads.Longitude)) + SIN(RADIANS(users.Latitude)) * SIN(RADIANS(ads.Latitude))))'), '<=', 'ads.radius')
->get();
}
It's not a traditional Eloquent relationship so you won't be able to eager load it, although I'm sure that would be possible with more work.
In order to find a user's ads, you would simply do something like the following...
$ads = (new \App\User::find($user_id))->ads();
Note that this assumes both your users table and ads table contains a longitude and latitude column. It also assumes your ads table has a radius column. You might need to rename some columns but it should give you what you need.
Also this assumes your radius is in miles. If you use km, instead of 69, use 111.1111.
I have made some changes to the version of you user3158900.
Made a scope out of the function and the lat/long from the user is variable so I need them as a input in the query.
I now have this:
public function scopeAdsInLocation($query, $from_latitude, $from_longitude)
{
$query = CampaignModel::
where(\DB::raw('111.1111 * DEGREES(ACOS(COS(RADIANS(' . $from_latitude . ')) * COS(RADIANS(campaigns.loc_lat)) * COS(RADIANS(' . $from_longitude .' -
campaigns.loc_long)) + SIN(RADIANS(' . $from_latitude . ')) * SIN(RADIANS(campaigns.loc_lat))))'), '<=', 'campaigns.loc_radius')
->get();
return $query;
}
I call it like this:
$ads = CampaignModel::adsInLocation(51.191320, 5.987772);
var_dump($ads);
This code works, but only if I set the radius to a fixed value. So when I replace campaigns.loc_radius with '100' it works. But with the radius each campaign has it doesn't seem to do the job. Do you know maybe why? Or have a solution for this.
Related
My application collects geolocation point from the user every certain amount of time, I am trying to use these points in order to calculate the distance from the first point through all of the points.
please note that when the user moves in a straight line, the geolocation points do not form a straight line, because the points I collect have a margin of error due to inaccuracy, thus I can't use something like Haversine formula because it will give incorrect value (longer distance than real distance)
and I can't use Google Maps Distance API because it calculates the distance between 2 points only, and it will be so expensive to call it 200 times to calculate distance through all points.
and I want to calculate this value on the server-side because of some security rules I have. so using the google maps SDK in the front end to calculate it is not an option either.
Any idea ...
One option would be to simplify the line, then run the data through the Google Roads API (assuming the travel is on roads), then measure the length of the resulting line (following the roads).
for anyone facing the same problem,I have followed this link
https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/roads/snap
here is my code in PHP
// snap the collected points from user to the nearest road using google API
$fields = array(
'path' => '60.170880,24.942795|60.170879,24.942796|60.170877,24.942796|60.170902,24.942654',
'key' => '<YOUR_KEY_HERE>'
);
$url = "https://roads.googleapis.com/v1/snapToRoads?" . http_build_query($fields, '', '&');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url );
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec($ch);
$response = json_decode($response);
$totalDistance = 0;
$previousPoint = null;
foreach ($response->snappedPoints as $pointOnRoad) {
if(!$previousPoint){
$previousPoint = $pointOnRoad;
continue;
}
$totalDistance += getDistance($pointOnRoad->location->latitude, $pointOnRoad->location->longitude,
$previousPoint->location->latitude, $previousPoint->location->longitude);
}
echo $totalDistance;
// calculate distance between 2 geo points
function getDistance($latitude1, $longitude1, $latitude2, $longitude2) {
$earth_radius = 6371;
$dLat = deg2rad($latitude2 - $latitude1);
$dLon = deg2rad($longitude2 - $longitude1);
$a = sin($dLat/2) * sin($dLat/2) + cos(deg2rad($latitude1)) * cos(deg2rad($latitude2)) * sin($dLon/2) * sin($dLon/2);
$c = 2 * asin(sqrt($a));
$d = $earth_radius * $c;
return $d;
}
We are having similar kind of requirement. There are 2 paths and we need to make sure that each node in 1st path (except start and end) are at least 100 KM away from each other of 2nd path.
Can you please share code snippet or logic behind this.
Using Haversine formula in loop will impact performance. So, please suggest some better solution.
Are there pre-existing libraries that will take a user input and transform it into a SQL WHERE clause?
For example given a database that has columns first_name, last_name, and address the user could input something like:
John State St
and the library would build a query such that it would return rows that match a guy named John that lived on State St (or a guy named State that lived on John St, for that matter).
It could also support things like specifying the column:
first_name:John address:State
I have some simple code to handle some of these cases already but it's getting a little unwieldily. I would think there are some pre-existing solutions to this problem but I'm having a hard time finding them. Generally, the problem is how to enable the user to easily search a structured database with a single input field.
In this matter you can break down string as multiple values using string manipulation, and then search for each word in each column using "or" conditions.
additionally you can define index on columns so as to achieve faster search.
You might have tried this technique since you mentioned, but you have to look up each word in each column
Jquery UI autocomplete could be what you are looking for. the css along with it is also necessary please see this link http://api.jqueryui.com/autocomplete/ for the .js ans css needed.
$( "#myInputBoxId" ).autocomplete({
source: "search.php",
minLength: 2,
select: function( event, ui ) {
log( ui.item ?
"Selected: " + ui.item.value + " aka " + ui.item.id :
"Nothing selected, input was " + this.value );
}
});
and in search.php
<?php
include 'config.php';
$results =array();
$req = "SELECT product_name "
."FROM table4 "
."WHERE product_name LIKE '%".$_REQUEST['term']."%' LIMIT 5";
$query = mysqli_query($con,$req);
while($row = mysqli_fetch_array($query))
{
array_push($results,$row['product_name']);
}
echo json_encode($results);
}
I'm kind of new to mongodb.
I want to use mongodb's geospatial feature to query locations in a boundary obtained from google maps. It's like withinBox(swLng, swLat, neLng, neLat). But it fails to give me correct results when the map zooms out, as neLng/neLat is less than swLng/swLat. It seems that this is calculated using x > swLng and x < neLng and y > swLat and y < neLat, not taking map projection into account.
So can I achieve the query by withinBox, or I have to adjust coordinates, or I should use near?
As I commented above, when northeast and southwest points cross the international date line, the query will fail as top right's y is smaller than bottom left's y.
If you are using mongodb with php and doctrine, you need make two queries and merge result on the client side like below:
if ($x1 > $x2) {
$qb->field('coordinates')->withinBox(-180, $y1, $x2, $y2);
$result1 = $qb->getQuery()->execute();
$qb->field('coordinates')->withinBox($x1, $y1, 180, $y2);
$result2 = $qb->getQuery()->execute();
$result = $result1->toArray() + $result2->toArray();
} else {
$qb->field('coordinates')->withinBox($x1, $y1, $x2, $y2);
$result = $qb->getQuery()->execute();
$result = $result->toArray();
}
I have found many calculations here and some php examples and most are just over my head.
I found this example:
SELECT b.zip_code, b.state,
(3956 * (2 * ASIN(SQRT(
POWER(SIN(((a.lat-b.lat)*0.017453293)/2),2) +
COS(a.lat*0.017453293) *
COS(b.lat*0.017453293) *
POWER(SIN(((a.lng-b.lng)*0.017453293)/2),2))))) AS distance
FROM zips a, zips b
WHERE
a.zip_code = '90210' ## I would use the users submitted value
GROUP BY distance
having distance <= 5; ## I would use the users submitted value
But, I am having trouble understanding how to implement the query with my database.
It looks like that query has all I need.
However, I cannot even find/understand what b.zip_code actually is! (whats the b. and zips a, zips b?)
I also do not need the state in the query.
My mySQL db structure is like this:
ZIP | LAT | LONG
33416 | 26.6654 | -80.0929
I wrote this in attempt to return some kind of results (not based on above query) but, it only kicks out one zip code.
## Just for a test BUT, in reality I desire to SELECT a zip code WHERE ZIP = the users submitted zip code
## not by a submitted lat lon. I left off the $connect var, assume it's there.
my $set1 = (26.6654 - 0.20);
my $set2 = (26.6654 + 0.20);
my $set3 = (-80.0929 - 0.143);
my $set4 = (-80.0929 + 0.143);
my $test123 = $connect->prepare(qq{SELECT `ZIP` FROM `POSTAL`
WHERE `LAT` >= ? AND `LAT` <= ?
AND `LONG` >= ? AND `LONG` <= ?}) or die "$DBI::errstr";
$test123->execute("$set1","$set2","$set3","$set4") or die "$DBI::errstr";
my $cntr;
while(#zip = $test123->fetchrow_array()) {
print qq~$zip[$cntr]~;
push(#zips,$zip[$cntr]);
$cntr++;
}
As you can see, I am quite the novice so, I need some hand holding here with verbose explanation.
So, in Perl, how can I push zip codes into an array from a USER SUBMITTED ZIP CODE and user submitted DISTANCE in miles. Can be a square instead of a circle, not really that critical of a feature. Faster is better.
I'll tackle the small but crucial part of the question:
However, I cannot even find/understand what b.zip_code actually is! (whats the "b." and "zips a, zips b"?)
Basically, the query joins two tables. BUT, both tables being joined are in fact the same table - "zips" (in other words, it joins "zips" table to itself"). Of course, since the rest of the query needs to understand when you are referring to the first copy of the "zips" table and when to the second copy of the "zips" table, you are giving a table alias to each copy - to wit, "a" and "b"'.
So, "b.xxx" means "column xxx from table zips, from the SECOND instance of that table being joined".
I don't see what's wrong with your first query. You have latitude and longitude in your database (if I'm understanding, you're comparing a single entry to all others). You don't need to submit or return the state that's just part of the example. Make the first query work like this:
my $query = "SELECT b.zip_code,
(3956 * (2 * ASIN(SQRT(
POWER(SIN(((a.lat-b.lat)*0.017453293)/2),2) +
COS(a.lat*0.017453293) *
COS(b.lat*0.017453293) *
POWER(SIN(((a.lng-b.lng)*0.017453293)/2),2))))) AS distance
FROM zips a, zips b WHERE
a.zip_code = ?
GROUP BY distance having distance <= ?";
my $sth = $dbh->prepare($query);
$sth->execute( $user_submitted_zip, $user_submitted_distance );
while( my ($zip, $distance) = $sth->fetchrow() ) ) {
# do something
}
This won't be that fast, but if you have a small record set ( less than 30k rows ) it should be fine. If you really want to go faster you should look into a search engine such as Sphinx which will do this for you.
fetchrow_array returns a list of list references, essentially a two-dimensional array, where each row represents a different result from the database query and each column represents a field from the query (in your case, there is only one field, or column, per row).
Calling while ($test123->fetchrow_array()) will cause an infinite loop as your program executes the query over and over again. If the query returns results, then the while condition will be satisfied and the loop will repeat. The usual idiom would be to say something more like for my $row ($test123->fetchrow_array()) { ..., which will only execute the query once and then iterate over the results.
Each result is a list reference, and the zip code you are interested in is in the first (and only) column, so you could accumulate the results in an array like this:
my #zips = (); # for final results
for my $row ($test123->fetchrow_array()) {
push #zips, $row->[0];
}
or even more concisely with Perl's map statement:
my #zips = map { $_->[0] } $test123->fetchrow_array()
which does the same thing.
I need to amend (via CONCAT, presumably) something to every wordpress post if it belongs to a certain category (say, category ID 7), but I'm struggling to get it to work.
To test, I'm first trying to select all the relevant posts. So far, I have the following:
SELECT post_title
FROM cruise_wp_posts
LEFT JOIN cruise_wp_term_relationships
ON cruise_wp_term_relationships.object_id = cruise_wp_posts.ID
WHERE term_taxonomy_id = 87;
However, it only lists posts that are only in category 87 - I need all posts that are in category 87 (and possibly other categories too)
I'm a MySQL newbie, and this is really breaking my brain.
Any pointers would be passionately welcomed.
The best way to do it is to filter it in as needed. This way the addition is made everywhere the_content is used and not just in the templates you modify.
<?php
function my_content_concat($the_content) {
if (in_category(7)) {
$the_content .= '<br /><br />foo!';
}
return $the_content;
}
add_filter('the_content', 'my_content_concat', 9);
?>
in_category can take the id, name or slug of your target category.
I put the filter at 9 so that it runs before WordPress texturizes the content. If you don't need that run it at 11.
Why not just use get_the_category( $id ) and ammend the text when you output the post?
$cat = get_the_category( $postID );
if ($cat == 7) {
//Add text here
}