I have a list of companies, each with a lat lng. In addition, each company has a radius within which they are prepared to work. What I need to do is be able to perform a search around a given location + radius and see if that radius overlaps with the working radius of any companies.
I'm using Lucene.net 2.9.4 (Umbraco) and am looking to use Spatial4n with it. I've currently got the Spatial4n 0.3 lib, and have compiled the lucene.net.contrib.spacial project against it (after having to mash together a bunch of code from the latest lucene repo ad some things seem to be missing).
So my question is, how would I a) go about indexing a company and their work radius? and b) search for companies who are prepared to work within a given search radius?
Can't answer a) but for b), get the distance between the 2 coordinates, and if it's less than the sum of both radii, then it's a match (i.e. overlap).
Get Distance:
public static double GetDistance(double latitude1, double longitude1, double latitude2, double longitude2, DistanceUnits distanceUnits = DistanceUnits.Miles)
{
try
{
// Earth radius in kilometers via NASA as of 2016 - https://nssdc.gsfc.nasa.gov/planetary/factsheet/earthfact.html
const double earthRadiusKilometers = 6378.137;
var latRadians = (latitude2 - latitude1).ToRadians();
var lonRadians = (longitude2 - longitude1).ToRadians();
var a = Math.Sin(latRadians / 2) *
Math.Sin(latRadians / 2) +
Math.Cos(latitude1.ToRadians()) *
Math.Cos(latitude2.ToRadians()) *
Math.Sin(lonRadians / 2) *
Math.Sin(lonRadians / 2);
var radianDistance = 2 * Math.Asin(Math.Min(1, Math.Sqrt(a)));
switch (distanceUnits)
{
case DistanceUnits.Kilometers:
{
return radianDistance * earthRadiusKilometers;
}
case DistanceUnits.Miles:
{
return radianDistance * ConvertKilometersToMiles(earthRadiusKilometers);
}
}
return radianDistance;
}
catch
{
return double.MaxValue;
}
}
public static double ConvertKilometersToMiles(double kilometers)
{
return kilometers * 0.621371192;
}
public enum DistanceUnits
{
Miles,
Kilometers,
Radians
}
Related
Kindly help me write this function
I have this list where each driver is listed with
1 current latitude longitude
2 onDuty or not
3 vehicle type
4 driver details
whenever user initiates search
function searches for
1 vehicel type user has chosen
2 AND if driver is on duty
3 AND if driver is within 10 km radius (using latitude and longitude)
and copy all eligible drivers to -> availableDriverList
distance calculating code
function getDistanceFromLatLonInKm(lat1,lon1,lat2,lon2) {
var R = 6371; // Radius of the earth in km
var dLat = deg2rad(lat2-lat1); // deg2rad below
var dLon = deg2rad(lon2-lon1);
var a =
Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) *
Math.sin(dLon/2) * Math.sin(dLon/2)
;
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = R * c; // Distance in km
return d;
}
function deg2rad(deg) {
return deg * (Math.PI/180)
}
Searching for a radius around a point requires that you perform a range comparison on the longitude and the latitude of each document. In a Firestore query you can only perform a range comparison on a single field, so with your current data structure you cannot perform the query you want.
There is a way to encode longitude and latitude into a single value that can be used for such comparisons, known as a Geohash. If you add a Geohash to each document, you can then perform the query. To learn how to do this, have a look at the Firestore documentation on performing Geoqueries.
I also recommend reading: Query for nearby locations. While for a different database, the limits and thus solution are the same. If you got the time, I also recommend watching this talk I gave a few years ago about how to add geoqueries to Firestore.
I have a situation where I need to calculate the distance between users. In this particular scenario I have:
Employer geolocation - One user one location.
Candidate geolocation - One location for each user but when the employer generates the list of candidates there are multiple candidates.
I'm currently successfully pushing geolocations down the wire and running a rudimentary distance formula on the client side to figure out the distance between the employer and each candidate on the fly and then showing/hiding the candidates, as per the request.
I've been told that I should be running the calculation on the server side and just pushing down a single number, ie. 10 representing 10km, for each candidate. Then running a filter on that number.
So far, I have only pushed collections and fields down the wire. Is it possible to run the formula below on the server side and just pass down one number and 'attach' it to a user?
The second question is, what would be best practice for Meteor?
I'm still learning to code, so apologies if this is a really obvious question.
Client Side
Path: List.js
specialisations = specialisations.filter(function(element){
let distance = Template.instance().distanceFromEmployerFilter.get();
let user = Meteor.users.findOne({_id: element.candidateUserId});
let candidateLat = user && user.profile && user.profile.address && user.profile.address.latitude;
let candidateLong = user && user.profile && user.profile.address && user.profile.address.longitude;
let company = CompanyDetails.findOne({employerUserId: Meteor.userId()});
let companyLat = company && company.latitude;
let companyLong = company && company.longitude;
var R = 6371; // Radius of the earth in km
var dLat = (companyLat-candidateLat) * (Math.PI/180);
var dLon = (companyLong-candidateLong) * (Math.PI/180);
var a =
Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos((candidateLat) * (Math.PI/180)) * Math.cos((companyLat) * (Math.PI/180)) *
Math.sin(dLon/2) * Math.sin(dLon/2)
;
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var distanceInKM = R * c; // Distance in km
if (distanceInKM <= distance) {
return true;
} else {
return false;
}
});
I'd do the filtering on the fetching of the candidates to display. Either as you publish/subscribe on your template or as you fetch in your helper:
Meteor.users.find({
"profile.address" : {
$near: {
$geometry: {
type: "Point" ,
coordinates: [ <Employerlongitude> , <Employerlatitude> ]
},
$maxDistance: <distance in meters>,
$minDistance: <distance in meters>
}}}).fetch();
If address is a 2d index. Specify coordinates in this order: “longitude, latitude.”
from Mongodb docs :
$near Specifies a point for which a geospatial query returns the
documents from nearest to farthest. The $near operator can specify
either a GeoJSON point or legacy coordinate point.
$minDistance & $maxDistance are optional
I have implemented the same function "distancebetween" as in Nerddinner. I created an airport repository and have these methods:
public IQueryable<AllAirports> ReturnAllAirportWithIn50milesOfAPoint(double lat, double lon)
{
var airports = from d in im.AllAirports
where DistanceBetween(lat, lon, (double)d.Lat, (double)d.Lon) < 1000.00
select d;
return airports;
}
[EdmFunction("AirTravelModel.Store", "DistanceBetween")]
public static double DistanceBetween(double lat1, double long1, double lat2, double long2)
{
throw new NotImplementedException("Only call through LINQ expression");
}
When I tested it, it shows:
base {"The specified method 'Double DistanceBetween(Double, Double, Double, Double)' on the type 'AirTravelMVC3.Models.Repository.AirportRepository' cannot be translated into a LINQ to Entities
store expression because no overload matches the passed arguments."} System.SystemException {System.NotSupportedException}
Do you have any ideas on why this happen? The only different between my work and nerddinner is that I used a POCO plugin in entity framework.
The SQL UDF is as follows, it works very well in the database:
CREATE FUNCTION [dbo].[DistanceBetween](#Lat1 as real,
#Long1 as real, #Lat2 as real, #Long2 as real)
RETURNS real
AS
BEGIN
DECLARE #dLat1InRad as float(53);
SET #dLat1InRad = #Lat1 * (PI()/180.0);
DECLARE #dLong1InRad as float(53);
SET #dLong1InRad = #Long1 * (PI()/180.0);
DECLARE #dLat2InRad as float(53);
SET #dLat2InRad = #Lat2 * (PI()/180.0);
DECLARE #dLong2InRad as float(53);
SET #dLong2InRad = #Long2 * (PI()/180.0);
DECLARE #dLongitude as float(53);
SET #dLongitude = #dLong2InRad - #dLong1InRad;
DECLARE #dLatitude as float(53);
SET #dLatitude = #dLat2InRad - #dLat1InRad;
/* Intermediate result a. */
DECLARE #a as float(53);
SET #a = SQUARE (SIN (#dLatitude / 2.0)) + COS (#dLat1InRad)
* COS (#dLat2InRad)
* SQUARE(SIN (#dLongitude / 2.0));
/* Intermediate result c (great circle distance in Radians). */
DECLARE #c as real;
SET #c = 2.0 * ATN2 (SQRT (#a), SQRT (1.0 - #a));
DECLARE #kEarthRadius as real;
/* SET kEarthRadius = 3956.0 miles */
SET #kEarthRadius = 6376.5; /* kms */
DECLARE #dDistance as real;
SET #dDistance = #kEarthRadius * #c;
return (#dDistance);
END
In order to do this using POCOs with the existing NerdDinner source I had to add this to the DinnerRepository class:
public IQueryable<Dinner> FindByLocation(float latitude, float longitude)
{
List<Dinner> resultList = new List<Dinner>();
var results = db.Database.SqlQuery<Dinner>("SELECT * FROM Dinners WHERE EventDate >= {0} AND dbo.DistanceBetween({1}, {2}, Latitude, Longitude) < 1000", DateTime.Now, latitude, longitude);
foreach (Dinner result in results)
{
resultList.Add(db.Dinners.Where(d => d.DinnerID == result.DinnerID).FirstOrDefault());
}
return resultList.AsQueryable<Dinner>();
}
Assuming DistanceBetween is implemented in c# The issue (as hinted at by #p.campbell) is that the query generator doesn't know how to calculate DistanceBetween
To get your code to work as is, you might need to do something like
public IQueryable<AllAirports> ReturnAllAirportWithIn50milesOfAPoint(double lat, double lon)
{
var airports = from d in im.AllAirports.ToList()
where DistanceBetween(lat, lon, (double)d.Lat, (double)d.Lon) < 1000.00
select d;
return airports;
}
the ToList() will force the AllAirports to evaluate to a list, then it can be evaluated in memory, using your c# function. Obviously this won't scale to a huge number of airports. If that was an issue, you might want to do a rough "box" query where you just do a cheap within-a-square expression to return a small number of airports, ToList that, and then call your distancebetween to refine the results.
I got this same exception after I made several changes to the DB schema and "updated the model from database".
After comparing my EDMX XML with the original from Nerd Dinner, I saw that mine had changed all of the types for the DistanceBetween function to "real", where Nerd Dinner had "float". Changing them back to float resolved the issue.
I am trying to build support for tiled vector data into some of our Google Maps v3 web maps, and I'm having a hard time figuring out how to find out which 256 x 256 tiles are visible in the current map viewport. I know that the information needed to figure this out is available if you create a google.maps.ImageMapType like here: Replacing GTileLayer in Google Maps v3, with ImageMapType, Tile bounding box?, but I'm obviously not doing this to bring in traditional pre-rendered map tiles.
So, a two part question:
What is the best way to find out which tiles are visible in the current viewport?
Once I have this information, what is the best way to go about converting it into lat/lng bounding boxes that can be used to request the necessary data? I know I could store this information on the server, but if there is an easy way to convert on the client it would be nice.
Here's what I came up with, with help from the documentation (http://code.google.com/apis/maps/documentation/javascript/maptypes.html, especially the "Map Coordinates" section) and a number of different sources:
function loadData() {
var bounds = map.getBounds(),
boundingBoxes = [],
boundsNeLatLng = bounds.getNorthEast(),
boundsSwLatLng = bounds.getSouthWest(),
boundsNwLatLng = new google.maps.LatLng(boundsNeLatLng.lat(), boundsSwLatLng.lng()),
boundsSeLatLng = new google.maps.LatLng(boundsSwLatLng.lat(), boundsNeLatLng.lng()),
zoom = map.getZoom(),
tiles = [],
tileCoordinateNw = pointToTile(boundsNwLatLng, zoom),
tileCoordinateSe = pointToTile(boundsSeLatLng, zoom),
tileColumns = tileCoordinateSe.x - tileCoordinateNw.x + 1;
tileRows = tileCoordinateSe.y - tileCoordinateNw.y + 1;
zfactor = Math.pow(2, zoom),
minX = tileCoordinateNw.x,
minY = tileCoordinateNw.y;
while (tileRows--) {
while (tileColumns--) {
tiles.push({
x: minX + tileColumns,
y: minY
});
}
minY++;
tileColumns = tileCoordinateSe.x - tileCoordinateNw.x + 1;
}
$.each(tiles, function(i, v) {
boundingBoxes.push({
ne: projection.fromPointToLatLng(new google.maps.Point(v.x * 256 / zfactor, v.y * 256 / zfactor)),
sw: projection.fromPointToLatLng(new google.maps.Point((v.x + 1) * 256 / zfactor, (v.y + 1) * 256 / zfactor))
});
});
$.each(boundingBoxes, function(i, v) {
var poly = new google.maps.Polygon({
map: map,
paths: [
v.ne,
new google.maps.LatLng(v.sw.lat(), v.ne.lng()),
v.sw,
new google.maps.LatLng(v.ne.lat(), v.sw.lng())
]
});
polygons.push(poly);
});
}
function pointToTile(latLng, z) {
var projection = new MercatorProjection();
var worldCoordinate = projection.fromLatLngToPoint(latLng);
var pixelCoordinate = new google.maps.Point(worldCoordinate.x * Math.pow(2, z), worldCoordinate.y * Math.pow(2, z));
var tileCoordinate = new google.maps.Point(Math.floor(pixelCoordinate.x / MERCATOR_RANGE), Math.floor(pixelCoordinate.y / MERCATOR_RANGE));
return tileCoordinate;
};
An explanation: Basically, everytime the map is panned or zoomed, I call the loadData function. This function calculates which tiles are in the map view, then iterates through the tiles that are already loaded and deletes the ones that are no longer in the view (I took this portion of code out, so you won't see it above). I then use the LatLngBounds stored in the boundingBoxes array to request data from the server.
Hope this helps someone else...
For more recent users, it's possible to get tile images from the sample code in the documentation on this page of the Google Maps Javascript API documentation.
Showing Pixel and Tile Coordinates
When streetview is not available for a certain location, I would like to find the nearest possible location with streetview?
The only way I could think of is.
radius = 0;
noOfPoints = 3;
while(radius < 10 miles){
radius = radius + 0.2 miles
points = calculate 4 * noOfPoints at this radius
loop(points)
{
if(streetview visibile for point)
bingo, break;
}
break if bingo;
noOfPOints = noOfPoints+1;
}
But this is ridiculously expensive even if I want to find streetview within a 10-mile radius and I am counting on luck to find streetview at one of these points, i.e, I could just miss an actual point with streetview.
can somebody please direct me towards a better approach??
You can try to use the StreetViewService to help you find a nearest existing street view:
var astorPlace = new google.maps.LatLng(40.729884, -73.990988);
var webService = new google.maps.StreetViewService();
/**Check in a perimeter of 50 meters**/
var checkaround = 50;
/** checkNearestStreetView is a valid callback function **/
webService.getPanoramaByLocation(astorPlace,checkaround ,checkNearestStreetView);
function checkNearestStreetView(panoData){
if(panoData){
if(panoData.location){
if(panoData.location.latLng){
/**Well done you can use a nearest existing street view coordinates**/
}
}
}
/** Else do something... **/
}